home *** CD-ROM | disk | FTP | other *** search
Text File | 2013-01-09 | 200.3 KB | 7,079 lines |
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- /*
- *
- *
- *
- *
- *
- *
- *
- *********************************** WARNING ***********************************
- *
- * Do not edit this file without understanding where it comes from,
- * Your changes are likely to be overwritten without warning.
- *
- * For more information on GCLI see:
- * - https://github.com/mozilla/gcli/blob/master/docs/index.md
- * - https://wiki.mozilla.org/DevTools/Features/GCLI
- *
- * The original source for this file is:
- * https://github.com/mozilla/gcli/
- *
- * This build of GCLI for Firefox comes from 4 bits of code:
- * - prefix-gcli.jsm: Initial commentary and EXPORTED_SYMBOLS
- * - console.js: Support code common to web content that is not part of the
- * default firefox chrome environment and is easy to shim.
- * - mini_require: A very basic commonjs AMD (Asynchronous Modules Definition)
- * 'require' implementation (which is just good enough to load GCLI). For
- * more, see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition.
- * This alleviates the need for requirejs (http://requirejs.org/) which is
- * used when running in the browser. This code is provided by dryice.
- * - A build of GCLI itself, packaged using dryice
- * - suffix-gcli.jsm - code to require the gcli object for EXPORTED_SYMBOLS.
- *
- * See Makefile.dryice.js for more details of this build.
- * For more details on dryice, see the https://github.com/mozilla/dryice
- *
- *******************************************************************************
- *
- *
- *
- *
- *
- *
- *
- *
- *
- */
-
- ///////////////////////////////////////////////////////////////////////////////
-
- var EXPORTED_SYMBOLS = [ "gcli" ];
-
-
- /**
- * Expose a Node object. This allows us to use the Node constants without
- * resorting to hardcoded numbers
- */
- var Node = Components.interfaces.nsIDOMNode;
-
-
- Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
- /**
- * Define setTimeout and clearTimeout to match the browser functions
- */
- var setTimeout;
- var clearTimeout;
-
- (function() {
- /**
- * The next value to be returned by setTimeout
- */
- var nextID = 1;
-
- /**
- * The map of outstanding timeouts
- */
- var timers = {};
-
- /**
- * Object to be passed to Timer.initWithCallback()
- */
- function TimerCallback(callback) {
- this._callback = callback;
- var interfaces = [ Components.interfaces.nsITimerCallback ];
- this.QueryInterface = XPCOMUtils.generateQI(interfaces);
- }
-
- TimerCallback.prototype.notify = function(timer) {
- try {
- for (var timerID in timers) {
- if (timers[timerID] === timer) {
- delete timers[timerID];
- break;
- }
- }
- this._callback.apply(null, []);
- }
- catch (ex) {
- console.error(ex);
- }
- };
-
- /**
- * Executes a code snippet or a function after specified delay.
- * This is designed to have the same interface contract as the browser
- * function.
- * @param callback is the function you want to execute after the delay.
- * @param delay is the number of milliseconds that the function call should
- * be delayed by. Note that the actual delay may be longer, see Notes below.
- * @return the ID of the timeout, which can be used later with
- * window.clearTimeout.
- */
- setTimeout = function setTimeout(callback, delay) {
- var timer = Components.classes["@mozilla.org/timer;1"]
- .createInstance(Components.interfaces.nsITimer);
-
- var timerID = nextID++;
- timers[timerID] = timer;
-
- timer.initWithCallback(new TimerCallback(callback), delay, timer.TYPE_ONE_SHOT);
- return timerID;
- };
-
- /**
- * Clears the delay set by window.setTimeout() and prevents the callback from
- * being executed (if it hasn't been executed already)
- * @param timerID the ID of the timeout you wish to clear, as returned by
- * window.setTimeout().
- */
- clearTimeout = function clearTimeout(timerID) {
- var timer = timers[timerID];
- if (timer) {
- timer.cancel();
- delete timers[timerID];
- }
- };
- })();
-
-
- /**
- * This creates a console object that somewhat replicates Firebug's console
- * object. It currently writes to dump(), but should write to the web
- * console's chrome error section (when it has one)
- */
- var console = {};
- (function() {
- /**
- * String utility to ensure that strings are a specified length. Strings
- * that are too long are truncated to the max length and the last char is
- * set to "_". Strings that are too short are left padded with spaces.
- *
- * @param {string} aStr
- * The string to format to the correct length
- * @param {number} aMaxLen
- * The maximum allowed length of the returned string
- * @param {number} aMinLen (optional)
- * The minimum allowed length of the returned string. If undefined,
- * then aMaxLen will be used
- * @param {object} aOptions (optional)
- * An object allowing format customization. The only customization
- * allowed currently is 'truncate' which can take the value "start" to
- * truncate strings from the start as opposed to the end.
- * @return {string}
- * The original string formatted to fit the specified lengths
- */
- function fmt(aStr, aMaxLen, aMinLen, aOptions) {
- if (aMinLen == null) {
- aMinLen = aMaxLen;
- }
- if (aStr == null) {
- aStr = "";
- }
- if (aStr.length > aMaxLen) {
- if (aOptions && aOptions.truncate == "start") {
- return "_" + aStr.substring(aStr.length - aMaxLen + 1);
- }
- else {
- return aStr.substring(0, aMaxLen - 1) + "_";
- }
- }
- if (aStr.length < aMinLen) {
- return Array(aMinLen - aStr.length + 1).join(" ") + aStr;
- }
- return aStr;
- }
-
- /**
- * Utility to extract the constructor name of an object.
- * Object.toString gives: "[object ?????]"; we want the "?????".
- *
- * @param {object} aObj
- * The object from which to extract the constructor name
- * @return {string}
- * The constructor name
- */
- function getCtorName(aObj) {
- return Object.prototype.toString.call(aObj).slice(8, -1);
- }
-
- /**
- * A single line stringification of an object designed for use by humans
- *
- * @param {any} aThing
- * The object to be stringified
- * @return {string}
- * A single line representation of aThing, which will generally be at
- * most 80 chars long
- */
- function stringify(aThing) {
- if (aThing === undefined) {
- return "undefined";
- }
-
- if (aThing === null) {
- return "null";
- }
-
- if (typeof aThing == "object") {
- var type = getCtorName(aThing);
- if (type == "XULElement") {
- return debugElement(aThing);
- }
- type = (type == "Object" ? "" : type + " ");
- var json;
- try {
- json = JSON.stringify(aThing);
- }
- catch (ex) {
- // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
- json = "{" + Object.keys(aThing).join(":..,") + ":.., " + "}";
- }
- return type + fmt(json, 50, 0);
- }
-
- var str = aThing.toString(); //.replace(/\s+/g, " ");
- return fmt(str, 80, 0);
- }
-
- /**
- * Create a simple debug representation of a given element.
- *
- * @param {nsIDOMElement} aElement
- * The element to debug
- * @return {string}
- * A simple single line representation of aElement
- */
- function debugElement(aElement) {
- return "<" + aElement.tagName +
- (aElement.id ? "#" + aElement.id : "") +
- (aElement.className ?
- "." + aElement.className.split(" ").join(" .") :
- "") +
- ">";
- }
-
- /**
- * A multi line stringification of an object, designed for use by humans
- *
- * @param {any} aThing
- * The object to be stringified
- * @return {string}
- * A multi line representation of aThing
- */
- function log(aThing) {
- if (aThing === null) {
- return "null\n";
- }
-
- if (aThing === undefined) {
- return "undefined\n";
- }
-
- if (typeof aThing == "object") {
- var reply = "";
- var type = getCtorName(aThing);
- if (type == "Error") {
- reply += " " + aThing.message + "\n";
- reply += logProperty("stack", aThing.stack);
- }
- else if (type == "XULElement") {
- reply += " " + debugElement(aThing) + " (XUL)\n";
- }
- else {
- var keys = Object.getOwnPropertyNames(aThing);
- if (keys.length > 0) {
- reply += type + "\n";
- keys.forEach(function(aProp) {
- reply += logProperty(aProp, aThing[aProp]);
- }, this);
- }
- else {
- reply += type + " (enumerated with for-in)\n";
- var prop;
- for (prop in aThing) {
- reply += logProperty(prop, aThing[prop]);
- }
- }
- }
-
- return reply;
- }
-
- return " " + aThing.toString() + "\n";
- }
-
- /**
- * Helper for log() which converts a property/value pair into an output
- * string
- *
- * @param {string} aProp
- * The name of the property to include in the output string
- * @param {object} aValue
- * Value assigned to aProp to be converted to a single line string
- * @return {string}
- * Multi line output string describing the property/value pair
- */
- function logProperty(aProp, aValue) {
- var reply = "";
- if (aProp == "stack" && typeof value == "string") {
- var trace = parseStack(aValue);
- reply += formatTrace(trace);
- }
- else {
- reply += " - " + aProp + " = " + stringify(aValue) + "\n";
- }
- return reply;
- }
-
- /**
- * Parse a stack trace, returning an array of stack frame objects, where
- * each has file/line/call members
- *
- * @param {string} aStack
- * The serialized stack trace
- * @return {object[]}
- * Array of { file: "...", line: NNN, call: "..." } objects
- */
- function parseStack(aStack) {
- var trace = [];
- aStack.split("\n").forEach(function(line) {
- if (!line) {
- return;
- }
- var at = line.lastIndexOf("@");
- var posn = line.substring(at + 1);
- trace.push({
- file: posn.split(":")[0],
- line: posn.split(":")[1],
- call: line.substring(0, at)
- });
- }, this);
- return trace;
- }
-
- /**
- * parseStack() takes output from an exception from which it creates the an
- * array of stack frame objects, this has the same output but using data from
- * Components.stack
- *
- * @param {string} aFrame
- * The stack frame from which to begin the walk
- * @return {object[]}
- * Array of { file: "...", line: NNN, call: "..." } objects
- */
- function getStack(aFrame) {
- if (!aFrame) {
- aFrame = Components.stack.caller;
- }
- var trace = [];
- while (aFrame) {
- trace.push({
- file: aFrame.filename,
- line: aFrame.lineNumber,
- call: aFrame.name
- });
- aFrame = aFrame.caller;
- }
- return trace;
- }
-
- /**
- * Take the output from parseStack() and convert it to nice readable
- * output
- *
- * @param {object[]} aTrace
- * Array of trace objects as created by parseStack()
- * @return {string} Multi line report of the stack trace
- */
- function formatTrace(aTrace) {
- var reply = "";
- aTrace.forEach(function(frame) {
- reply += fmt(frame.file, 20, 20, { truncate: "start" }) + " " +
- fmt(frame.line, 5, 5) + " " +
- fmt(frame.call, 75, 75) + "\n";
- });
- return reply;
- }
-
- /**
- * Create a function which will output a concise level of output when used
- * as a logging function
- *
- * @param {string} aLevel
- * A prefix to all output generated from this function detailing the
- * level at which output occurred
- * @return {function}
- * A logging function
- * @see createMultiLineDumper()
- */
- function createDumper(aLevel) {
- return function() {
- var args = Array.prototype.slice.call(arguments, 0);
- var data = args.map(function(arg) {
- return stringify(arg);
- });
- dump(aLevel + ": " + data.join(", ") + "\n");
- };
- }
-
- /**
- * Create a function which will output more detailed level of output when
- * used as a logging function
- *
- * @param {string} aLevel
- * A prefix to all output generated from this function detailing the
- * level at which output occurred
- * @return {function}
- * A logging function
- * @see createDumper()
- */
- function createMultiLineDumper(aLevel) {
- return function() {
- dump(aLevel + "\n");
- var args = Array.prototype.slice.call(arguments, 0);
- args.forEach(function(arg) {
- dump(log(arg));
- });
- };
- }
-
- /**
- * Build out the console object
- */
- console.debug = createMultiLineDumper("debug");
- console.log = createDumper("log");
- console.info = createDumper("info");
- console.warn = createDumper("warn");
- console.error = createMultiLineDumper("error");
- console.trace = function Console_trace() {
- var trace = getStack(Components.stack.caller);
- dump(formatTrace(trace) + "\n");
- },
- console.clear = function Console_clear() {};
-
- console.dir = createMultiLineDumper("dir");
- console.dirxml = createMultiLineDumper("dirxml");
- console.group = createDumper("group");
- console.groupEnd = createDumper("groupEnd");
-
- })();
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-
- /**
- * Define a module along with a payload.
- * @param moduleName Name for the payload
- * @param deps Ignored. For compatibility with CommonJS AMD Spec
- * @param payload Function with (require, exports, module) params
- */
- function define(moduleName, deps, payload) {
- if (typeof moduleName != "string") {
- console.error(this.depth + " Error: Module name is not a string.");
- console.trace();
- return;
- }
-
- if (arguments.length == 2) {
- payload = deps;
- }
-
- if (define.debugDependencies) {
- console.log("define: " + moduleName + " -> " + payload.toString()
- .slice(0, 40).replace(/\n/, '\\n').replace(/\r/, '\\r') + "...");
- }
-
- if (moduleName in define.modules) {
- console.error(this.depth + " Error: Redefining module: " + moduleName);
- }
- define.modules[moduleName] = payload;
- }
-
- /**
- * The global store of un-instantiated modules
- */
- define.modules = {};
-
- /**
- * Should we console.log on module definition/instantiation/requirement?
- */
- define.debugDependencies = false;
-
-
- /**
- * Self executing function in which Domain is defined, and attached to define
- */
- (function() {
- /**
- * We invoke require() in the context of a Domain so we can have multiple
- * sets of modules running separate from each other.
- * This contrasts with JSMs which are singletons, Domains allows us to
- * optionally load a CommonJS module twice with separate data each time.
- * Perhaps you want 2 command lines with a different set of commands in each,
- * for example.
- */
- function Domain() {
- this.modules = {};
-
- if (define.debugDependencies) {
- this.depth = "";
- }
- }
-
- /**
- * Lookup module names and resolve them by calling the definition function if
- * needed.
- * There are 2 ways to call this, either with an array of dependencies and a
- * callback to call when the dependencies are found (which can happen
- * asynchronously in an in-page context) or with a single string an no
- * callback where the dependency is resolved synchronously and returned.
- * The API is designed to be compatible with the CommonJS AMD spec and
- * RequireJS.
- * @param deps A name, or array of names for the payload
- * @param callback Function to call when the dependencies are resolved
- * @return The module required or undefined for array/callback method
- */
- Domain.prototype.require = function(deps, callback) {
- if (Array.isArray(deps)) {
- var params = deps.map(function(dep) {
- return this.lookup(dep);
- }, this);
- if (callback) {
- callback.apply(null, params);
- }
- return undefined;
- }
- else {
- return this.lookup(deps);
- }
- };
-
- /**
- * Lookup module names and resolve them by calling the definition function if
- * needed.
- * @param moduleName A name for the payload to lookup
- * @return The module specified by aModuleName or null if not found
- */
- Domain.prototype.lookup = function(moduleName) {
- if (moduleName in this.modules) {
- var module = this.modules[moduleName];
- if (define.debugDependencies) {
- console.log(this.depth + " Using module: " + moduleName);
- }
- return module;
- }
-
- if (!(moduleName in define.modules)) {
- console.error(this.depth + " Missing module: " + moduleName);
- return null;
- }
-
- var module = define.modules[moduleName];
-
- if (define.debugDependencies) {
- console.log(this.depth + " Compiling module: " + moduleName);
- }
-
- if (typeof module == "function") {
- if (define.debugDependencies) {
- this.depth += ".";
- }
-
- var exports = {};
- try {
- module(this.require.bind(this), exports, { id: moduleName, uri: "" });
- }
- catch (ex) {
- console.error("Error using module: " + moduleName, ex);
- throw ex;
- }
- module = exports;
-
- if (define.debugDependencies) {
- this.depth = this.depth.slice(0, -1);
- }
- }
-
- // cache the resulting module object for next time
- this.modules[moduleName] = module;
-
- return module;
- };
-
- /**
- * Expose the Domain constructor and a global domain (on the define function
- * to avoid exporting more than we need. This is a common pattern with
- * require systems)
- */
- define.Domain = Domain;
- define.globalDomain = new Domain();
- })();
-
- /**
- * Expose a default require function which is the require of the global
- * sandbox to make it easy to use.
- */
- var require = define.globalDomain.require.bind(define.globalDomain);
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- var mozl10n = {};
-
- (function(aMozl10n) {
- var temp = {};
- Components.utils.import("resource://gre/modules/Services.jsm", temp);
- var stringBundle = temp.Services.strings.createBundle(
- "chrome://browser/locale/devtools/gclicommands.properties");
-
- /**
- * Lookup a string in the GCLI string bundle
- * @param name The name to lookup
- * @return The looked up name
- */
- aMozl10n.lookup = function(name) {
- try {
- return stringBundle.GetStringFromName(name);
- }
- catch (ex) {
- throw new Error("Failure in lookup('" + name + "')");
- }
- };
-
- /**
- * Lookup a string in the GCLI string bundle
- * @param name The name to lookup
- * @param swaps An array of swaps. See stringBundle.formatStringFromName
- * @return The looked up name
- */
- aMozl10n.lookupFormat = function(name, swaps) {
- try {
- return stringBundle.formatStringFromName(name, swaps, swaps.length);
- }
- catch (ex) {
- throw new Error("Failure in lookupFormat('" + name + "')");
- }
- };
-
- })(mozl10n);
-
- define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/javascript', 'gcli/types/node', 'gcli/cli', 'gcli/ui/inputter', 'gcli/ui/arg_fetch', 'gcli/ui/menu', 'gcli/ui/focus'], function(require, exports, module) {
-
- // The API for use by command authors
- exports.addCommand = require('gcli/canon').addCommand;
- exports.removeCommand = require('gcli/canon').removeCommand;
- exports.lookup = mozl10n.lookup;
- exports.lookupFormat = mozl10n.lookupFormat;
-
- // Internal startup process. Not exported
- require('gcli/types/basic').startup();
- require('gcli/types/javascript').startup();
- require('gcli/types/node').startup();
- require('gcli/cli').startup();
-
- var Requisition = require('gcli/cli').Requisition;
- var cli = require('gcli/cli');
- var Inputter = require('gcli/ui/inputter').Inputter;
- var ArgFetcher = require('gcli/ui/arg_fetch').ArgFetcher;
- var CommandMenu = require('gcli/ui/menu').CommandMenu;
- var FocusManager = require('gcli/ui/focus').FocusManager;
-
- var jstype = require('gcli/types/javascript');
- var nodetype = require('gcli/types/node');
-
- /**
- * API for use by HUDService only.
- * This code is internal and subject to change without notice.
- */
- exports._internal = {
- require: require,
- define: define,
- console: console,
-
- /**
- * createView() for Firefox requires an options object with the following
- * members:
- * - contentDocument: From the window of the attached tab
- * - chromeDocument: GCLITerm.document
- * - environment.hudId: GCLITerm.hudId
- * - jsEnvironment.globalObject: 'window'
- * - jsEnvironment.evalFunction: 'eval' in a sandbox
- * - inputElement: GCLITerm.inputNode
- * - completeElement: GCLITerm.completeNode
- * - gcliTerm: GCLITerm
- * - hintElement: GCLITerm.hintNode
- * - inputBackgroundElement: GCLITerm.inputStack
- */
- createView: function(opts) {
- opts.autoHide = true;
- opts.requisition = new Requisition(opts.environment, opts.chromeDocument);
- opts.completionPrompt = '';
-
- jstype.setGlobalObject(opts.jsEnvironment.globalObject);
- nodetype.setDocument(opts.contentDocument);
- cli.setEvalFunction(opts.jsEnvironment.evalFunction);
-
- // Create a FocusManager for the various parts to register with
- if (!opts.focusManager) {
- opts.debug = true;
- opts.focusManager = new FocusManager({ document: opts.chromeDocument });
- }
-
- opts.inputter = new Inputter(opts);
- opts.inputter.update();
- if (opts.gcliTerm) {
- opts.focusManager.onFocus.add(opts.gcliTerm.show, opts.gcliTerm);
- opts.focusManager.onBlur.add(opts.gcliTerm.hide, opts.gcliTerm);
- opts.focusManager.addMonitoredElement(opts.gcliTerm.hintNode, 'gcliTerm');
- }
-
- if (opts.hintElement) {
- opts.menu = new CommandMenu(opts.chromeDocument, opts.requisition);
- opts.hintElement.appendChild(opts.menu.element);
-
- opts.argFetcher = new ArgFetcher(opts.chromeDocument, opts.requisition);
- opts.hintElement.appendChild(opts.argFetcher.element);
-
- opts.menu.onCommandChange();
- }
- },
-
- /**
- * Undo the effects of createView() to prevent memory leaks
- */
- removeView: function(opts) {
- opts.hintElement.removeChild(opts.menu.element);
- opts.menu.destroy();
- opts.hintElement.removeChild(opts.argFetcher.element);
- opts.argFetcher.destroy();
-
- opts.inputter.destroy();
- opts.focusManager.removeMonitoredElement(opts.gcliTerm.hintNode, 'gcliTerm');
- opts.focusManager.onFocus.remove(opts.gcliTerm.show, opts.gcliTerm);
- opts.focusManager.onBlur.remove(opts.gcliTerm.hide, opts.gcliTerm);
- opts.focusManager.destroy();
-
- cli.unsetEvalFunction();
- nodetype.unsetDocument();
- jstype.unsetGlobalObject();
-
- opts.requisition.destroy();
- },
-
- commandOutputManager: require('gcli/canon').commandOutputManager
- };
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic'], function(require, exports, module) {
- var canon = exports;
-
-
- var util = require('gcli/util');
- var l10n = require('gcli/l10n');
-
- var types = require('gcli/types');
- var Status = require('gcli/types').Status;
- var BooleanType = require('gcli/types/basic').BooleanType;
-
-
- /**
- * A lookup hash of our registered commands
- */
- var commands = {};
-
- /**
- * A sorted list of command names, we regularly want them in order, so pre-sort
- */
- var commandNames = [];
-
- /**
- * Implement the localization algorithm for any documentation objects (i.e.
- * description and manual) in a command.
- * @param data The data assigned to a description or manual property
- * @param onUndefined If data == null, should we return the data untouched or
- * lookup a 'we don't know' key in it's place.
- */
- function lookup(data, onUndefined) {
- if (data == null) {
- if (onUndefined) {
- return l10n.lookup(onUndefined);
- }
-
- return data;
- }
-
- if (typeof data === 'string') {
- return data;
- }
-
- if (typeof data === 'object') {
- if (data.key) {
- return l10n.lookup(data.key);
- }
-
- var locales = l10n.getPreferredLocales();
- var translated;
- locales.some(function(locale) {
- translated = data[locale];
- return translated != null;
- });
- if (translated != null) {
- return translated;
- }
-
- console.error('Can\'t find locale in descriptions: ' +
- 'locales=' + JSON.stringify(locales) + ', ' +
- 'description=' + JSON.stringify(data));
- return '(No description)';
- }
-
- return l10n.lookup(onUndefined);
- }
-
- /**
- * The command object is mostly just setup around a commandSpec (as passed to
- * #addCommand()).
- */
- function Command(commandSpec) {
- Object.keys(commandSpec).forEach(function(key) {
- this[key] = commandSpec[key];
- }, this);
-
- if (!this.name) {
- throw new Error('All registered commands must have a name');
- }
-
- if (this.params == null) {
- this.params = [];
- }
- if (!Array.isArray(this.params)) {
- throw new Error('command.params must be an array in ' + this.name);
- }
-
- this.description = 'description' in this ? this.description : undefined;
- this.description = lookup(this.description, 'canonDescNone');
- this.manual = 'manual' in this ? this.manual : undefined;
- this.manual = lookup(this.manual);
-
- // At this point this.params has nested param groups. We want to flatten it
- // out and replace the param object literals with Parameter objects
- var paramSpecs = this.params;
- this.params = [];
-
- // Track if the user is trying to mix default params and param groups.
- // All the non-grouped parameters must come before all the param groups
- // because non-grouped parameters can be assigned positionally, so their
- // index is important. We don't want 'holes' in the order caused by
- // parameter groups.
- var usingGroups = false;
-
- // In theory this could easily be made recursive, so param groups could
- // contain nested param groups. Current thinking is that the added
- // complexity for the UI probably isn't worth it, so this implementation
- // prevents nesting.
- paramSpecs.forEach(function(spec) {
- if (!spec.group) {
- if (usingGroups) {
- console.error('Parameters can\'t come after param groups.' +
- ' Ignoring ' + this.name + '/' + spec.name);
- }
- else {
- var param = new Parameter(spec, this, null);
- this.params.push(param);
- }
- }
- else {
- spec.params.forEach(function(ispec) {
- var param = new Parameter(ispec, this, spec.group);
- this.params.push(param);
- }, this);
-
- usingGroups = true;
- }
- }, this);
- }
-
- canon.Command = Command;
-
-
- /**
- * A wrapper for a paramSpec so we can sort out shortened versions names for
- * option switches
- */
- function Parameter(paramSpec, command, groupName) {
- this.command = command || { name: 'unnamed' };
-
- Object.keys(paramSpec).forEach(function(key) {
- this[key] = paramSpec[key];
- }, this);
-
- this.description = 'description' in this ? this.description : undefined;
- this.description = lookup(this.description, 'canonDescNone');
- this.manual = 'manual' in this ? this.manual : undefined;
- this.manual = lookup(this.manual);
- this.groupName = groupName;
-
- if (!this.name) {
- throw new Error('In ' + this.command.name +
- ': all params must have a name');
- }
-
- var typeSpec = this.type;
- this.type = types.getType(typeSpec);
- if (this.type == null) {
- console.error('Known types: ' + types.getTypeNames().join(', '));
- throw new Error('In ' + this.command.name + '/' + this.name +
- ': can\'t find type for: ' + JSON.stringify(typeSpec));
- }
-
- // boolean parameters have an implicit defaultValue:false, which should
- // not be changed. See the docs.
- if (this.type instanceof BooleanType) {
- if ('defaultValue' in this) {
- console.error('In ' + this.command.name + '/' + this.name +
- ': boolean parameters can not have a defaultValue.' +
- ' Ignoring');
- }
- this.defaultValue = false;
- }
-
- // Check the defaultValue for validity.
- // Both undefined and null get a pass on this test. undefined is used when
- // there is no defaultValue, and null is used when the parameter is
- // optional, neither are required to parse and stringify.
- if (this.defaultValue != null) {
- try {
- var defaultText = this.type.stringify(this.defaultValue);
- var defaultConversion = this.type.parseString(defaultText);
- if (defaultConversion.getStatus() !== Status.VALID) {
- console.error('In ' + this.command.name + '/' + this.name +
- ': Error round tripping defaultValue. status = ' +
- defaultConversion.getStatus());
- }
- }
- catch (ex) {
- console.error('In ' + this.command.name + '/' + this.name +
- ': ' + ex);
- }
- }
- }
-
- /**
- * Does the given name uniquely identify this param (among the other params
- * in this command)
- * @param name The name to check
- */
- Parameter.prototype.isKnownAs = function(name) {
- if (name === '--' + this.name) {
- return true;
- }
- return false;
- };
-
- /**
- * Is the user required to enter data for this parameter? (i.e. has
- * defaultValue been set to something other than undefined)
- */
- Parameter.prototype.isDataRequired = function() {
- return this.defaultValue === undefined;
- };
-
- /**
- * Are we allowed to assign data to this parameter using positional
- * parameters?
- */
- Parameter.prototype.isPositionalAllowed = function() {
- return this.groupName == null;
- };
-
- canon.Parameter = Parameter;
-
-
- /**
- * Add a command to the canon of known commands.
- * This function is exposed to the outside world (via gcli/index). It is
- * documented in docs/index.md for all the world to see.
- * @param commandSpec The command and its metadata.
- * @return The new command
- */
- canon.addCommand = function addCommand(commandSpec) {
- var command = new Command(commandSpec);
- commands[commandSpec.name] = command;
- commandNames.push(commandSpec.name);
- commandNames.sort();
-
- canon.canonChange();
- return command;
- };
-
- /**
- * Remove an individual command. The opposite of #addCommand().
- * @param commandOrName Either a command name or the command itself.
- */
- canon.removeCommand = function removeCommand(commandOrName) {
- var name = typeof commandOrName === 'string' ?
- commandOrName :
- commandOrName.name;
- delete commands[name];
- commandNames = commandNames.filter(function(test) {
- return test !== name;
- });
-
- canon.canonChange();
- };
-
- /**
- * Retrieve a command by name
- * @param name The name of the command to retrieve
- */
- canon.getCommand = function getCommand(name) {
- return commands[name];
- };
-
- /**
- * Get an array of all the registered commands.
- */
- canon.getCommands = function getCommands() {
- // return Object.values(commands);
- return Object.keys(commands).map(function(name) {
- return commands[name];
- }, this);
- };
-
- /**
- * Get an array containing the names of the registered commands.
- */
- canon.getCommandNames = function getCommandNames() {
- return commandNames.slice(0);
- };
-
- /**
- * Enable people to be notified of changes to the list of commands
- */
- canon.canonChange = util.createEvent('canon.canonChange');
-
- /**
- * CommandOutputManager stores the output objects generated by executed
- * commands.
- *
- * CommandOutputManager is exposed (via canon.commandOutputManager) to the the
- * outside world and could (but shouldn't) be used before gcli.startup() has
- * been called. This could should be defensive to that where possible, and we
- * should certainly document if the use of it or similar will fail if used too
- * soon.
- */
- function CommandOutputManager() {
- this._event = util.createEvent('CommandOutputManager');
- }
-
- /**
- * Call this method to notify the manager (and therefore all listeners) of a
- * new or updated command output.
- * @param output The command output object that has been created or updated.
- */
- CommandOutputManager.prototype.sendCommandOutput = function(output) {
- this._event({ output: output });
- };
-
- /**
- * Register a function to be called whenever there is a new command output
- * object.
- */
- CommandOutputManager.prototype.addListener = function(fn, ctx) {
- this._event.add(fn, ctx);
- };
-
- /**
- * Undo the effects of CommandOutputManager.addListener()
- */
- CommandOutputManager.prototype.removeListener = function(fn, ctx) {
- this._event.remove(fn, ctx);
- };
-
- canon.CommandOutputManager = CommandOutputManager;
-
- /**
- * We maintain a global command output manager for the majority case where
- * there is only one important set of outputs.
- */
- canon.commandOutputManager = new CommandOutputManager();
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/util', ['require', 'exports', 'module' ], function(require, exports, module) {
-
- /*
- * This module is a Pilot-Lite. It exports a number of objects that replicate
- * parts of the Pilot project. It aims to be mostly API compatible, while
- * removing the submodule complexity and helping us make things work inside
- * Firefox.
- * The Pilot compatible exports are: console/dom/event
- *
- * In addition it contains a small event library similar to EventEmitter but
- * which makes it harder to mistake the event in use.
- */
-
-
- //------------------------------------------------------------------------------
-
- /**
- * Create an event.
- * For use as follows:
- *
- * function Hat() {
- * this.putOn = createEvent();
- * ...
- * }
- * Hat.prototype.adorn = function(person) {
- * this.putOn({ hat: hat, person: person });
- * ...
- * }
- *
- * var hat = new Hat();
- * hat.putOn.add(function(ev) {
- * console.log('The hat ', ev.hat, ' has is worn by ', ev.person);
- * }, scope);
- *
- * @param name Optional name to help with debugging
- */
- exports.createEvent = function(name) {
- var handlers = [];
-
- /**
- * This is how the event is triggered.
- * @param ev The event object to be passed to the event listeners
- */
- var event = function(ev) {
- // Use for rather than forEach because it step debugs better, which is
- // important for debugging events
- for (var i = 0; i < handlers.length; i++) {
- var handler = handlers[i];
- handler.func.call(handler.scope, ev);
- }
- };
-
- /**
- * Add a new handler function
- * @param func The function to call when this event is triggered
- * @param scope Optional 'this' object for the function call
- */
- event.add = function(func, scope) {
- handlers.push({ func: func, scope: scope });
- };
-
- /**
- * Remove a handler function added through add(). Both func and scope must
- * be strict equals (===) the values used in the call to add()
- * @param func The function to call when this event is triggered
- * @param scope Optional 'this' object for the function call
- */
- event.remove = function(func, scope) {
- handlers = handlers.filter(function(test) {
- return test.func !== func && test.scope !== scope;
- });
- };
-
- /**
- * Remove all handlers.
- * Reset the state of this event back to it's post create state
- */
- event.removeAll = function() {
- handlers = [];
- };
-
- return event;
- };
-
-
- //------------------------------------------------------------------------------
-
- var dom = {};
-
- var NS_XHTML = 'http://www.w3.org/1999/xhtml';
-
- /**
- * Pass-through to createElement or createElementNS
- * @param doc The document in which to create the element
- * @param tag The name of the tag to create
- * @param ns Custom namespace, HTML/XHTML is assumed if this is missing
- * @returns The created element
- */
- dom.createElement = function(doc, tag, ns) {
- // If we've not been given a namespace, but the document is XML, then we
- // use an XHTML namespace, otherwise we use HTML
- if (ns == null && doc.xmlVersion != null) {
- ns = NS_XHTML;
- }
- if (ns == null) {
- return doc.createElement(tag);
- }
- else {
- return doc.createElementNS(ns, tag);
- }
- };
-
- /**
- * Remove all the child nodes from this node
- * @param elem The element that should have it's children removed
- */
- dom.clearElement = function(elem) {
- while (elem.hasChildNodes()) {
- elem.removeChild(elem.firstChild);
- }
- };
-
- /**
- * Create a style element in the document head, and add the given CSS text to
- * it.
- * @param cssText The CSS declarations to append
- * @param doc The document element to work from
- */
- dom.importCss = function(cssText, doc) {
- doc = doc || document;
-
- var style = dom.createElement(doc, 'style');
- style.appendChild(doc.createTextNode(cssText));
-
- var head = doc.getElementsByTagName('head')[0] || doc.documentElement;
- head.appendChild(style);
-
- return style;
- };
-
- /**
- * Using setInnerHtml(foo) rather than innerHTML = foo allows us to enable
- * tweaks in XHTML documents.
- */
- dom.setInnerHtml = function(elem, html) {
- if (!this.document || elem.namespaceURI === NS_XHTML) {
- try {
- dom.clearElement(elem);
- var range = elem.ownerDocument.createRange();
- html = '<div xmlns="' + NS_XHTML + '">' + html + '</div>';
- elem.appendChild(range.createContextualFragment(html));
- }
- catch (ex) {
- elem.innerHTML = html;
- }
- }
- else {
- elem.innerHTML = html;
- }
- };
-
- exports.dom = dom;
-
-
- //------------------------------------------------------------------------------
-
- /**
- * Various event utilities
- */
- var event = {};
-
- /**
- * Keyboard handling is a mess. http://unixpapa.com/js/key.html
- * It would be good to use DOM L3 Keyboard events,
- * http://www.w3.org/TR/2010/WD-DOM-Level-3-Events-20100907/#events-keyboardevents
- * however only Webkit supports them, and there isn't a shim on Monernizr:
- * https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
- * and when the code that uses this KeyEvent was written, nothing was clear,
- * so instead, we're using this unmodern shim:
- * http://stackoverflow.com/questions/5681146/chrome-10-keyevent-or-something-similar-to-firefoxs-keyevent
- * See BUG 664991: GCLI's keyboard handling should be updated to use DOM-L3
- * https://bugzilla.mozilla.org/show_bug.cgi?id=664991
- */
- if ('KeyEvent' in this) {
- event.KeyEvent = this.KeyEvent;
- }
- else {
- event.KeyEvent = {
- DOM_VK_CANCEL: 3,
- DOM_VK_HELP: 6,
- DOM_VK_BACK_SPACE: 8,
- DOM_VK_TAB: 9,
- DOM_VK_CLEAR: 12,
- DOM_VK_RETURN: 13,
- DOM_VK_ENTER: 14,
- DOM_VK_SHIFT: 16,
- DOM_VK_CONTROL: 17,
- DOM_VK_ALT: 18,
- DOM_VK_PAUSE: 19,
- DOM_VK_CAPS_LOCK: 20,
- DOM_VK_ESCAPE: 27,
- DOM_VK_SPACE: 32,
- DOM_VK_PAGE_UP: 33,
- DOM_VK_PAGE_DOWN: 34,
- DOM_VK_END: 35,
- DOM_VK_HOME: 36,
- DOM_VK_LEFT: 37,
- DOM_VK_UP: 38,
- DOM_VK_RIGHT: 39,
- DOM_VK_DOWN: 40,
- DOM_VK_PRINTSCREEN: 44,
- DOM_VK_INSERT: 45,
- DOM_VK_DELETE: 46,
- DOM_VK_0: 48,
- DOM_VK_1: 49,
- DOM_VK_2: 50,
- DOM_VK_3: 51,
- DOM_VK_4: 52,
- DOM_VK_5: 53,
- DOM_VK_6: 54,
- DOM_VK_7: 55,
- DOM_VK_8: 56,
- DOM_VK_9: 57,
- DOM_VK_SEMICOLON: 59,
- DOM_VK_EQUALS: 61,
- DOM_VK_A: 65,
- DOM_VK_B: 66,
- DOM_VK_C: 67,
- DOM_VK_D: 68,
- DOM_VK_E: 69,
- DOM_VK_F: 70,
- DOM_VK_G: 71,
- DOM_VK_H: 72,
- DOM_VK_I: 73,
- DOM_VK_J: 74,
- DOM_VK_K: 75,
- DOM_VK_L: 76,
- DOM_VK_M: 77,
- DOM_VK_N: 78,
- DOM_VK_O: 79,
- DOM_VK_P: 80,
- DOM_VK_Q: 81,
- DOM_VK_R: 82,
- DOM_VK_S: 83,
- DOM_VK_T: 84,
- DOM_VK_U: 85,
- DOM_VK_V: 86,
- DOM_VK_W: 87,
- DOM_VK_X: 88,
- DOM_VK_Y: 89,
- DOM_VK_Z: 90,
- DOM_VK_CONTEXT_MENU: 93,
- DOM_VK_NUMPAD0: 96,
- DOM_VK_NUMPAD1: 97,
- DOM_VK_NUMPAD2: 98,
- DOM_VK_NUMPAD3: 99,
- DOM_VK_NUMPAD4: 100,
- DOM_VK_NUMPAD5: 101,
- DOM_VK_NUMPAD6: 102,
- DOM_VK_NUMPAD7: 103,
- DOM_VK_NUMPAD8: 104,
- DOM_VK_NUMPAD9: 105,
- DOM_VK_MULTIPLY: 106,
- DOM_VK_ADD: 107,
- DOM_VK_SEPARATOR: 108,
- DOM_VK_SUBTRACT: 109,
- DOM_VK_DECIMAL: 110,
- DOM_VK_DIVIDE: 111,
- DOM_VK_F1: 112,
- DOM_VK_F2: 113,
- DOM_VK_F3: 114,
- DOM_VK_F4: 115,
- DOM_VK_F5: 116,
- DOM_VK_F6: 117,
- DOM_VK_F7: 118,
- DOM_VK_F8: 119,
- DOM_VK_F9: 120,
- DOM_VK_F10: 121,
- DOM_VK_F11: 122,
- DOM_VK_F12: 123,
- DOM_VK_F13: 124,
- DOM_VK_F14: 125,
- DOM_VK_F15: 126,
- DOM_VK_F16: 127,
- DOM_VK_F17: 128,
- DOM_VK_F18: 129,
- DOM_VK_F19: 130,
- DOM_VK_F20: 131,
- DOM_VK_F21: 132,
- DOM_VK_F22: 133,
- DOM_VK_F23: 134,
- DOM_VK_F24: 135,
- DOM_VK_NUM_LOCK: 144,
- DOM_VK_SCROLL_LOCK: 145,
- DOM_VK_COMMA: 188,
- DOM_VK_PERIOD: 190,
- DOM_VK_SLASH: 191,
- DOM_VK_BACK_QUOTE: 192,
- DOM_VK_OPEN_BRACKET: 219,
- DOM_VK_BACK_SLASH: 220,
- DOM_VK_CLOSE_BRACKET: 221,
- DOM_VK_QUOTE: 222,
- DOM_VK_META: 224
- };
- }
-
- exports.event = event;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/l10n', ['require', 'exports', 'module' ], function(require, exports, module) {
-
- Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
- Components.utils.import('resource://gre/modules/Services.jsm');
-
- XPCOMUtils.defineLazyGetter(this, 'stringBundle', function () {
- return Services.strings.createBundle('chrome://browser/locale/devtools/gcli.properties');
- });
-
- /*
- * Not supported when embedded - we're doing things the Mozilla way not the
- * require.js way.
- */
- exports.registerStringsSource = function(modulePath) {
- throw new Error('registerStringsSource is not available in mozilla');
- };
-
- exports.unregisterStringsSource = function(modulePath) {
- throw new Error('unregisterStringsSource is not available in mozilla');
- };
-
- exports.lookupSwap = function(key, swaps) {
- throw new Error('lookupSwap is not available in mozilla');
- };
-
- exports.lookupPlural = function(key, ord, swaps) {
- throw new Error('lookupPlural is not available in mozilla');
- };
-
- exports.getPreferredLocales = function() {
- return [ 'root' ];
- };
-
- /** @see lookup() in lib/gcli/l10n.js */
- exports.lookup = function(key) {
- try {
- return stringBundle.GetStringFromName(key);
- }
- catch (ex) {
- console.error('Failed to lookup ', key, ex);
- return key;
- }
- };
-
- /** @see lookupFormat in lib/gcli/l10n.js */
- exports.lookupFormat = function(key, swaps) {
- try {
- return stringBundle.formatStringFromName(key, swaps, swaps.length);
- }
- catch (ex) {
- console.error('Failed to format ', key, ex);
- return key;
- }
- };
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/types', ['require', 'exports', 'module' , 'gcli/argument'], function(require, exports, module) {
- var types = exports;
-
-
- var Argument = require('gcli/argument').Argument;
-
-
- /**
- * Some types can detect validity, that is to say they can distinguish between
- * valid and invalid values.
- * We might want to change these constants to be numbers for better performance
- */
- var Status = {
- /**
- * The conversion process worked without any problem, and the value is
- * valid. There are a number of failure states, so the best way to check
- * for failure is (x !== Status.VALID)
- */
- VALID: {
- toString: function() { return 'VALID'; },
- valueOf: function() { return 0; }
- },
-
- /**
- * A conversion process failed, however it was noted that the string
- * provided to 'parse()' could be VALID by the addition of more characters,
- * so the typing may not be actually incorrect yet, just unfinished.
- * @see Status.ERROR
- */
- INCOMPLETE: {
- toString: function() { return 'INCOMPLETE'; },
- valueOf: function() { return 1; }
- },
-
- /**
- * The conversion process did not work, the value should be null and a
- * reason for failure should have been provided. In addition some
- * completion values may be available.
- * @see Status.INCOMPLETE
- */
- ERROR: {
- toString: function() { return 'ERROR'; },
- valueOf: function() { return 2; }
- },
-
- /**
- * A combined status is the worser of the provided statuses. The statuses
- * can be provided either as a set of arguments or a single array
- */
- combine: function() {
- var combined = Status.VALID;
- for (var i = 0; i < arguments.length; i++) {
- var status = arguments[i];
- if (Array.isArray(status)) {
- status = Status.combine.apply(null, status);
- }
- if (status > combined) {
- combined = status;
- }
- }
- return combined;
- }
- };
- types.Status = Status;
-
- /**
- * The type.parse() method converts an Argument into a value, Conversion is
- * a wrapper to that value.
- * Conversion is needed to collect a number of properties related to that
- * conversion in one place, i.e. to handle errors and provide traceability.
- * @param value The result of the conversion
- * @param arg The data from which the conversion was made
- * @param status See the Status values [VALID|INCOMPLETE|ERROR] defined above.
- * The default status is Status.VALID.
- * @param message If status=ERROR, there should be a message to describe the
- * error. A message is not needed unless for other statuses, but could be
- * present for any status including VALID (in the case where we want to note a
- * warning, for example).
- * See BUG 664676: GCLI conversion error messages should be localized
- * @param predictions If status=INCOMPLETE, there could be predictions as to
- * the options available to complete the input.
- * We generally expect there to be about 7 predictions (to match human list
- * comprehension ability) however it is valid to provide up to about 20,
- * or less. It is the job of the predictor to decide a smart cut-off.
- * For example if there are 4 very good matches and 4 very poor ones,
- * probably only the 4 very good matches should be presented.
- * The predictions are presented either as an array of prediction objects or as
- * a function which returns this array when called with no parameters.
- * Each prediction object has the following shape:
- * {
- * name: '...', // textual completion. i.e. what the cli uses
- * value: { ... }, // value behind the textual completion
- * incomplete: true // this completion is only partial (optional)
- * }
- * The 'incomplete' property could be used to denote a valid completion which
- * could have sub-values (e.g. for tree navigation).
- */
- function Conversion(value, arg, status, message, predictions) {
- // The result of the conversion process. Will be null if status != VALID
- this.value = value;
-
- // Allow us to trace where this Conversion came from
- this.arg = arg;
- if (arg == null) {
- throw new Error('Missing arg');
- }
-
- this._status = status || Status.VALID;
- this.message = message;
- this.predictions = predictions;
- }
-
- types.Conversion = Conversion;
-
- /**
- * Ensure that all arguments that are part of this conversion know what they
- * are assigned to.
- * @param assignment The Assignment (param/conversion link) to inform the
- * argument about.
- */
- Conversion.prototype.assign = function(assignment) {
- this.arg.assign(assignment);
- };
-
- /**
- * Work out if there is information provided in the contained argument.
- */
- Conversion.prototype.isDataProvided = function() {
- var argProvided = this.arg.text !== '';
- return this.value !== undefined || argProvided;
- };
-
- /**
- * 2 conversions are equal if and only if their args are equal (argEquals) and
- * their values are equal (valueEquals).
- * @param that The conversion object to compare against.
- */
- Conversion.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null) {
- return false;
- }
- return this.valueEquals(that) && this.argEquals(that);
- };
-
- /**
- * Check that the value in this conversion is strict equal to the value in the
- * provided conversion.
- * @param that The conversion to compare values with
- */
- Conversion.prototype.valueEquals = function(that) {
- return this.value === that.value;
- };
-
- /**
- * Check that the argument in this conversion is equal to the value in the
- * provided conversion as defined by the argument (i.e. arg.equals).
- * @param that The conversion to compare arguments with
- */
- Conversion.prototype.argEquals = function(that) {
- return this.arg.equals(that.arg);
- };
-
- /**
- * Accessor for the status of this conversion
- */
- Conversion.prototype.getStatus = function(arg) {
- return this._status;
- };
-
- /**
- * Defined by the toString() value provided by the argument
- */
- Conversion.prototype.toString = function() {
- return this.arg.toString();
- };
-
- /**
- * If status === INCOMPLETE, then we may be able to provide predictions as to
- * how the argument can be completed.
- * @return An array of items, where each item is an object with the following
- * properties:
- * - name (mandatory): Displayed to the user, and typed in. No whitespace
- * - description (optional): Short string for display in a tool-tip
- * - manual (optional): Longer description which details usage
- * - incomplete (optional): Indicates that the prediction if used should not
- * be considered necessarily sufficient, which typically will mean that the
- * UI should not append a space to the completion
- * - value (optional): If a value property is present, this will be used as the
- * value of the conversion, otherwise the item itself will be used.
- */
- Conversion.prototype.getPredictions = function() {
- if (typeof this.predictions === 'function') {
- return this.predictions();
- }
- return this.predictions || [];
- };
-
- /**
- * ArrayConversion is a special Conversion, needed because arrays are converted
- * member by member rather then as a whole, which means we can track the
- * conversion if individual array elements. So an ArrayConversion acts like a
- * normal Conversion (which is needed as Assignment requires a Conversion) but
- * it can also be devolved into a set of Conversions for each array member.
- */
- function ArrayConversion(conversions, arg) {
- this.arg = arg;
- this.conversions = conversions;
- this.value = conversions.map(function(conversion) {
- return conversion.value;
- }, this);
-
- this._status = Status.combine(conversions.map(function(conversion) {
- return conversion.getStatus();
- }));
-
- // This message is just for reporting errors like "not enough values"
- // rather that for problems with individual values.
- this.message = '';
-
- // Predictions are generally provided by individual values
- this.predictions = [];
- }
-
- ArrayConversion.prototype = Object.create(Conversion.prototype);
-
- ArrayConversion.prototype.assign = function(assignment) {
- this.conversions.forEach(function(conversion) {
- conversion.assign(assignment);
- }, this);
- this.assignment = assignment;
- };
-
- ArrayConversion.prototype.getStatus = function(arg) {
- if (arg && arg.conversion) {
- return arg.conversion.getStatus();
- }
- return this._status;
- };
-
- ArrayConversion.prototype.isDataProvided = function() {
- return this.conversions.length > 0;
- };
-
- ArrayConversion.prototype.valueEquals = function(that) {
- if (!(that instanceof ArrayConversion)) {
- throw new Error('Can\'t compare values with non ArrayConversion');
- }
-
- if (this.value === that.value) {
- return true;
- }
-
- if (this.value.length !== that.value.length) {
- return false;
- }
-
- for (var i = 0; i < this.conversions.length; i++) {
- if (!this.conversions[i].valueEquals(that.conversions[i])) {
- return false;
- }
- }
-
- return true;
- };
-
- ArrayConversion.prototype.toString = function() {
- return '[ ' + this.conversions.map(function(conversion) {
- return conversion.toString();
- }, this).join(', ') + ' ]';
- };
-
- types.ArrayConversion = ArrayConversion;
-
-
- /**
- * Most of our types are 'static' e.g. there is only one type of 'string',
- * however some types like 'selection' and 'deferred' are customizable.
- * The basic Type type isn't useful, but does provide documentation about what
- * types do.
- */
- function Type() {
- }
-
- /**
- * Convert the given <tt>value</tt> to a string representation.
- * Where possible, there should be round-tripping between values and their
- * string representations.
- */
- Type.prototype.stringify = function(value) {
- throw new Error('Not implemented');
- };
-
- /**
- * Convert the given <tt>arg</tt> to an instance of this type.
- * Where possible, there should be round-tripping between values and their
- * string representations.
- * @param arg An instance of <tt>Argument</tt> to convert.
- * @return Conversion
- */
- Type.prototype.parse = function(arg) {
- throw new Error('Not implemented');
- };
-
- /**
- * A convenience method for times when you don't have an argument to parse
- * but instead have a string.
- * @see #parse(arg)
- */
- Type.prototype.parseString = function(str) {
- return this.parse(new Argument(str));
- },
-
- /**
- * The plug-in system, and other things need to know what this type is
- * called. The name alone is not enough to fully specify a type. Types like
- * 'selection' and 'deferred' need extra data, however this function returns
- * only the name, not the extra data.
- */
- Type.prototype.name = undefined;
-
- /**
- * If there is some concept of a higher value, return it,
- * otherwise return undefined.
- */
- Type.prototype.increment = function(value) {
- return undefined;
- };
-
- /**
- * If there is some concept of a lower value, return it,
- * otherwise return undefined.
- */
- Type.prototype.decrement = function(value) {
- return undefined;
- };
-
- /**
- * There is interesting information (like predictions) in a conversion of
- * nothing, the output of this can sometimes be customized.
- * @return Conversion
- */
- Type.prototype.getDefault = undefined;
-
- types.Type = Type;
-
- /**
- * Private registry of types
- * Invariant: types[name] = type.name
- */
- var registeredTypes = {};
-
- types.getTypeNames = function() {
- return Object.keys(registeredTypes);
- };
-
- /**
- * Add a new type to the list available to the system.
- * You can pass 2 things to this function - either an instance of Type, in
- * which case we return this instance when #getType() is called with a 'name'
- * that matches type.name.
- * Also you can pass in a constructor (i.e. function) in which case when
- * #getType() is called with a 'name' that matches Type.prototype.name we will
- * pass the typeSpec into this constructor.
- */
- types.registerType = function(type) {
- if (typeof type === 'object') {
- if (type instanceof Type) {
- if (!type.name) {
- throw new Error('All registered types must have a name');
- }
- registeredTypes[type.name] = type;
- }
- else {
- throw new Error('Can\'t registerType using: ' + type);
- }
- }
- else if (typeof type === 'function') {
- if (!type.prototype.name) {
- throw new Error('All registered types must have a name');
- }
- registeredTypes[type.prototype.name] = type;
- }
- else {
- throw new Error('Unknown type: ' + type);
- }
- };
-
- types.registerTypes = function registerTypes(newTypes) {
- Object.keys(newTypes).forEach(function(name) {
- var type = newTypes[name];
- type.name = name;
- newTypes.registerType(type);
- });
- };
-
- /**
- * Remove a type from the list available to the system
- */
- types.deregisterType = function(type) {
- delete registeredTypes[type.name];
- };
-
- /**
- * Find a type, previously registered using #registerType()
- */
- types.getType = function(typeSpec) {
- var type;
- if (typeof typeSpec === 'string') {
- type = registeredTypes[typeSpec];
- if (typeof type === 'function') {
- type = new type();
- }
- return type;
- }
-
- if (typeof typeSpec === 'object') {
- if (!typeSpec.name) {
- throw new Error('Missing \'name\' member to typeSpec');
- }
-
- type = registeredTypes[typeSpec.name];
- if (typeof type === 'function') {
- type = new type(typeSpec);
- }
- return type;
- }
-
- throw new Error('Can\'t extract type from ' + typeSpec);
- };
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/argument', ['require', 'exports', 'module' ], function(require, exports, module) {
- var argument = exports;
-
-
- /**
- * We record where in the input string an argument comes so we can report
- * errors against those string positions.
- * @param text The string (trimmed) that contains the argument
- * @param prefix Knowledge of quotation marks and whitespace used prior to the
- * text in the input string allows us to re-generate the original input from
- * the arguments.
- * @param suffix Any quotation marks and whitespace used after the text.
- * Whitespace is normally placed in the prefix to the succeeding argument, but
- * can be used here when this is the last argument.
- * @constructor
- */
- function Argument(text, prefix, suffix) {
- if (text === undefined) {
- this.text = '';
- this.prefix = '';
- this.suffix = '';
- }
- else {
- this.text = text;
- this.prefix = prefix !== undefined ? prefix : '';
- this.suffix = suffix !== undefined ? suffix : '';
- }
- }
-
- /**
- * Return the result of merging these arguments.
- * case and some of the arguments are in quotation marks?
- */
- Argument.prototype.merge = function(following) {
- // Is it possible that this gets called when we're merging arguments
- // for the single string?
- return new Argument(
- this.text + this.suffix + following.prefix + following.text,
- this.prefix, following.suffix);
- };
-
- /**
- * Returns a new Argument like this one but with the text set to
- * <tt>replText</tt> and the end adjusted to fit.
- * @param replText Text to replace the old text value
- */
- Argument.prototype.beget = function(replText, options) {
- var prefix = this.prefix;
- var suffix = this.suffix;
-
- var quote = (replText.indexOf(' ') >= 0 || replText.length == 0) ?
- '\'' : '';
-
- if (options) {
- prefix = (options.prefixSpace ? ' ' : '') + quote;
- suffix = quote;
- }
-
- return new Argument(replText, prefix, suffix);
- };
-
- /**
- * Is there any visible content to this argument?
- */
- Argument.prototype.isBlank = function() {
- return this.text === '' &&
- this.prefix.trim() === '' &&
- this.suffix.trim() === '';
- };
-
- /**
- * We need to keep track of which assignment we've been assigned to
- */
- Argument.prototype.assign = function(assignment) {
- this.assignment = assignment;
- };
-
- /**
- * Sub-classes of Argument are collections of arguments, getArgs() gets access
- * to the members of the collection in order to do things like re-create input
- * command lines. For the simple Argument case it's just an array containing
- * only this.
- */
- Argument.prototype.getArgs = function() {
- return [ this ];
- };
-
- /**
- * We define equals to mean all arg properties are strict equals.
- * Used by Conversion.argEquals and Conversion.equals and ultimately
- * Assignment.equals to avoid reporting a change event when a new conversion
- * is assigned.
- */
- Argument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null || !(that instanceof Argument)) {
- return false;
- }
-
- return this.text === that.text &&
- this.prefix === that.prefix && this.suffix === that.suffix;
- };
-
- /**
- * Helper when we're putting arguments back together
- */
- Argument.prototype.toString = function() {
- // BUG 664207: We should re-escape escaped characters
- // But can we do that reliably?
- return this.prefix + this.text + this.suffix;
- };
-
- /**
- * Merge an array of arguments into a single argument.
- * All Arguments in the array are expected to have the same emitter
- */
- Argument.merge = function(argArray, start, end) {
- start = (start === undefined) ? 0 : start;
- end = (end === undefined) ? argArray.length : end;
-
- var joined;
- for (var i = start; i < end; i++) {
- var arg = argArray[i];
- if (!joined) {
- joined = arg;
- }
- else {
- joined = joined.merge(arg);
- }
- }
- return joined;
- };
-
- argument.Argument = Argument;
-
-
- /**
- * ScriptArgument is a marker that the argument is designed to be Javascript.
- * It also implements the special rules that spaces after the { or before the
- * } are part of the pre/suffix rather than the content.
- */
- function ScriptArgument(text, prefix, suffix) {
- this.text = text;
- this.prefix = prefix !== undefined ? prefix : '';
- this.suffix = suffix !== undefined ? suffix : '';
-
- while (this.text.charAt(0) === ' ') {
- this.prefix = this.prefix + ' ';
- this.text = this.text.substring(1);
- }
-
- while (this.text.charAt(this.text.length - 1) === ' ') {
- this.suffix = ' ' + this.suffix;
- this.text = this.text.slice(0, -1);
- }
- }
-
- ScriptArgument.prototype = Object.create(Argument.prototype);
-
- /**
- * Returns a new Argument like this one but with the text set to
- * <tt>replText</tt> and the end adjusted to fit.
- * @param replText Text to replace the old text value
- */
- ScriptArgument.prototype.beget = function(replText, options) {
- var prefix = this.prefix;
- var suffix = this.suffix;
-
- var quote = (replText.indexOf(' ') >= 0 || replText.length == 0) ?
- '\'' : '';
-
- if (options && options.normalize) {
- prefix = '{ ';
- suffix = ' }';
- }
-
- return new ScriptArgument(replText, prefix, suffix);
- };
-
- argument.ScriptArgument = ScriptArgument;
-
-
- /**
- * Commands like 'echo' with a single string argument, and used with the
- * special format like: 'echo a b c' effectively have a number of arguments
- * merged together.
- */
- function MergedArgument(args, start, end) {
- if (!Array.isArray(args)) {
- throw new Error('args is not an array of Arguments');
- }
-
- if (start === undefined) {
- this.args = args;
- }
- else {
- this.args = args.slice(start, end);
- }
-
- var arg = Argument.merge(this.args);
- this.text = arg.text;
- this.prefix = arg.prefix;
- this.suffix = arg.suffix;
- }
-
- MergedArgument.prototype = Object.create(Argument.prototype);
-
- /**
- * Keep track of which assignment we've been assigned to, and allow the
- * original args to do the same.
- */
- MergedArgument.prototype.assign = function(assignment) {
- this.args.forEach(function(arg) {
- arg.assign(assignment);
- }, this);
-
- this.assignment = assignment;
- };
-
- MergedArgument.prototype.getArgs = function() {
- return this.args;
- };
-
- MergedArgument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null || !(that instanceof MergedArgument)) {
- return false;
- }
-
- // We might need to add a check that args is the same here
-
- return this.text === that.text &&
- this.prefix === that.prefix && this.suffix === that.suffix;
- };
-
- argument.MergedArgument = MergedArgument;
-
-
- /**
- * TrueNamedArguments are for when we have an argument like --verbose which
- * has a boolean value, and thus the opposite of '--verbose' is ''.
- */
- function TrueNamedArgument(name, arg) {
- this.arg = arg;
- this.text = arg ? arg.text : '--' + name;
- this.prefix = arg ? arg.prefix : ' ';
- this.suffix = arg ? arg.suffix : '';
- }
-
- TrueNamedArgument.prototype = Object.create(Argument.prototype);
-
- TrueNamedArgument.prototype.assign = function(assignment) {
- if (this.arg) {
- this.arg.assign(assignment);
- }
- this.assignment = assignment;
- };
-
- TrueNamedArgument.prototype.getArgs = function() {
- // NASTY! getArgs has a fairly specific use: in removing used arguments
- // from a command line. Unlike other arguments which are EITHER used
- // in assignments directly OR grouped in things like MergedArguments,
- // TrueNamedArgument is used raw from the UI, or composed of another arg
- // from the CLI, so we return both here so they can both be removed.
- return this.arg ? [ this, this.arg ] : [ this ];
- };
-
- TrueNamedArgument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null || !(that instanceof TrueNamedArgument)) {
- return false;
- }
-
- return this.text === that.text &&
- this.prefix === that.prefix && this.suffix === that.suffix;
- };
-
- argument.TrueNamedArgument = TrueNamedArgument;
-
-
- /**
- * FalseNamedArguments are for when we don't have an argument like --verbose
- * which has a boolean value, and thus the opposite of '' is '--verbose'.
- */
- function FalseNamedArgument() {
- this.text = '';
- this.prefix = '';
- this.suffix = '';
- }
-
- FalseNamedArgument.prototype = Object.create(Argument.prototype);
-
- FalseNamedArgument.prototype.getArgs = function() {
- return [ ];
- };
-
- FalseNamedArgument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null || !(that instanceof FalseNamedArgument)) {
- return false;
- }
-
- return this.text === that.text &&
- this.prefix === that.prefix && this.suffix === that.suffix;
- };
-
- argument.FalseNamedArgument = FalseNamedArgument;
-
-
- /**
- * A named argument is for cases where we have input in one of the following
- * formats:
- * <ul>
- * <li>--param value
- * <li>-p value
- * <li>--pa value
- * <li>-p:value
- * <li>--param=value
- * <li>etc
- * </ul>
- * The general format is:
- * /--?{unique-param-name-prefix}[ :=]{value}/
- * We model this as a normal argument but with a long prefix.
- */
- function NamedArgument(nameArg, valueArg) {
- this.nameArg = nameArg;
- this.valueArg = valueArg;
-
- this.text = valueArg.text;
- this.prefix = nameArg.toString() + valueArg.prefix;
- this.suffix = valueArg.suffix;
- }
-
- NamedArgument.prototype = Object.create(Argument.prototype);
-
- NamedArgument.prototype.assign = function(assignment) {
- this.nameArg.assign(assignment);
- this.valueArg.assign(assignment);
- this.assignment = assignment;
- };
-
- NamedArgument.prototype.getArgs = function() {
- return [ this.nameArg, this.valueArg ];
- };
-
- NamedArgument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null) {
- return false;
- }
-
- if (!(that instanceof NamedArgument)) {
- return false;
- }
-
- // We might need to add a check that nameArg and valueArg are the same
-
- return this.text === that.text &&
- this.prefix === that.prefix && this.suffix === that.suffix;
- };
-
- argument.NamedArgument = NamedArgument;
-
-
- /**
- * An argument the groups together a number of plain arguments together so they
- * can be jointly assigned to a single array parameter
- */
- function ArrayArgument() {
- this.args = [];
- }
-
- ArrayArgument.prototype = Object.create(Argument.prototype);
-
- ArrayArgument.prototype.addArgument = function(arg) {
- this.args.push(arg);
- };
-
- ArrayArgument.prototype.addArguments = function(args) {
- Array.prototype.push.apply(this.args, args);
- };
-
- ArrayArgument.prototype.getArguments = function() {
- return this.args;
- };
-
- ArrayArgument.prototype.assign = function(assignment) {
- this.args.forEach(function(arg) {
- arg.assign(assignment);
- }, this);
-
- this.assignment = assignment;
- };
-
- ArrayArgument.prototype.getArgs = function() {
- return this.args;
- };
-
- ArrayArgument.prototype.equals = function(that) {
- if (this === that) {
- return true;
- }
- if (that == null) {
- return false;
- }
-
- if (!(that instanceof ArrayArgument)) {
- return false;
- }
-
- if (this.args.length !== that.args.length) {
- return false;
- }
-
- for (var i = 0; i < this.args.length; i++) {
- if (!this.args[i].equals(that.args[i])) {
- return false;
- }
- }
-
- return true;
- };
-
- /**
- * Helper when we're putting arguments back together
- */
- ArrayArgument.prototype.toString = function() {
- return '{' + this.args.map(function(arg) {
- return arg.toString();
- }, this).join(',') + '}';
- };
-
- argument.ArrayArgument = ArrayArgument;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/argument'], function(require, exports, module) {
-
-
- var l10n = require('gcli/l10n');
- var types = require('gcli/types');
- var Type = require('gcli/types').Type;
- var Status = require('gcli/types').Status;
- var Conversion = require('gcli/types').Conversion;
- var ArrayConversion = require('gcli/types').ArrayConversion;
-
- var Argument = require('gcli/argument').Argument;
- var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
- var FalseNamedArgument = require('gcli/argument').FalseNamedArgument;
- var ArrayArgument = require('gcli/argument').ArrayArgument;
-
-
- /**
- * Registration and de-registration.
- */
- exports.startup = function() {
- types.registerType(StringType);
- types.registerType(NumberType);
- types.registerType(BooleanType);
- types.registerType(BlankType);
- types.registerType(SelectionType);
- types.registerType(DeferredType);
- types.registerType(ArrayType);
- };
-
- exports.shutdown = function() {
- types.unregisterType(StringType);
- types.unregisterType(NumberType);
- types.unregisterType(BooleanType);
- types.unregisterType(BlankType);
- types.unregisterType(SelectionType);
- types.unregisterType(DeferredType);
- types.unregisterType(ArrayType);
- };
-
-
- /**
- * 'string' the most basic string type that doesn't need to convert
- */
- function StringType(typeSpec) {
- if (typeSpec != null) {
- throw new Error('StringType can not be customized');
- }
- }
-
- StringType.prototype = Object.create(Type.prototype);
-
- StringType.prototype.stringify = function(value) {
- if (value == null) {
- return '';
- }
- return value.toString();
- };
-
- StringType.prototype.parse = function(arg) {
- if (arg.text == null || arg.text === '') {
- return new Conversion(null, arg, Status.INCOMPLETE, '');
- }
- return new Conversion(arg.text, arg);
- };
-
- StringType.prototype.name = 'string';
-
- exports.StringType = StringType;
-
-
- /**
- * We don't currently plan to distinguish between integers and floats
- */
- function NumberType(typeSpec) {
- if (typeSpec) {
- this._min = typeSpec.min;
- this._max = typeSpec.max;
- this._step = typeSpec.step || 1;
- }
- else {
- this._step = 1;
- }
- }
-
- NumberType.prototype = Object.create(Type.prototype);
-
- NumberType.prototype.stringify = function(value) {
- if (value == null) {
- return '';
- }
- return '' + value;
- };
-
- NumberType.prototype.getMin = function() {
- if (this._min) {
- if (typeof this._min === 'function') {
- return this._min();
- }
- if (typeof this._min === 'number') {
- return this._min;
- }
- }
- return 0;
- };
-
- NumberType.prototype.getMax = function() {
- if (this._max) {
- if (typeof this._max === 'function') {
- return this._max();
- }
- if (typeof this._max === 'number') {
- return this._max;
- }
- }
- return undefined;
- };
-
- NumberType.prototype.parse = function(arg) {
- if (arg.text.replace(/\s/g, '').length === 0) {
- return new Conversion(null, arg, Status.INCOMPLETE, '');
- }
-
- var value = parseInt(arg.text, 10);
- if (isNaN(value)) {
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookupFormat('typesNumberNan', [ arg.text ]));
- }
-
- if (this.getMax() != null && value > this.getMax()) {
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookupFormat('typesNumberMax', [ value, this.getMax() ]));
- }
-
- if (value < this.getMin()) {
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookupFormat('typesNumberMin', [ value, this.getMin() ]));
- }
-
- return new Conversion(value, arg);
- };
-
- NumberType.prototype.decrement = function(value) {
- if (typeof value !== 'number' || isNaN(value)) {
- return this.getMax() || 1;
- }
- var newValue = value - this._step;
- // Snap to the nearest incremental of the step
- newValue = Math.ceil(newValue / this._step) * this._step;
- return this._boundsCheck(newValue);
- };
-
- NumberType.prototype.increment = function(value) {
- if (typeof value !== 'number' || isNaN(value)) {
- return this.getMin();
- }
- var newValue = value + this._step;
- // Snap to the nearest incremental of the step
- newValue = Math.floor(newValue / this._step) * this._step;
- if (this.getMax() == null) {
- return newValue;
- }
- return this._boundsCheck(newValue);
- };
-
- /**
- * Return the input value so long as it is within the max/min bounds. If it is
- * lower than the minimum, return the minimum. If it is bigger than the maximum
- * then return the maximum.
- */
- NumberType.prototype._boundsCheck = function(value) {
- var min = this.getMin();
- if (value < min) {
- return min;
- }
- var max = this.getMax();
- if (value > max) {
- return max;
- }
- return value;
- };
-
- NumberType.prototype.name = 'number';
-
- exports.NumberType = NumberType;
-
- /**
- * One of a known set of options
- */
- function SelectionType(typeSpec) {
- if (typeSpec) {
- Object.keys(typeSpec).forEach(function(key) {
- this[key] = typeSpec[key];
- }, this);
- }
- }
-
- SelectionType.prototype = Object.create(Type.prototype);
-
- SelectionType.prototype.stringify = function(value) {
- var name = null;
- var lookup = this.getLookup();
- lookup.some(function(item) {
- var test = (item.value == null) ? item : item.value;
- if (test === value) {
- name = item.name;
- return true;
- }
- return false;
- }, this);
- return name;
- };
-
- /**
- * There are several ways to get selection data. This unifies them into one
- * single function.
- * @return A map of names to values.
- */
- SelectionType.prototype.getLookup = function() {
- if (this.lookup) {
- if (typeof this.lookup === 'function') {
- return this.lookup();
- }
- return this.lookup;
- }
-
- if (Array.isArray(this.data)) {
- this.lookup = this._dataToLookup(this.data);
- return this.lookup;
- }
-
- if (typeof(this.data) === 'function') {
- return this._dataToLookup(this.data());
- }
-
- throw new Error('SelectionType has no data');
- };
-
- /**
- * Selection can be provided with either a lookup object (in the 'lookup'
- * property) or an array of strings (in the 'data' property). Internally we
- * always use lookup, so we need a way to convert a 'data' array to a lookup.
- */
- SelectionType.prototype._dataToLookup = function(data) {
- return data.map(function(option) {
- return { name: option, value: option };
- });
- };
-
- /**
- * Return a list of possible completions for the given arg.
- * This code is very similar to CommandType._findPredictions(). If you are
- * making changes to this code, you should check there too.
- * @param arg The initial input to match
- * @return A trimmed lookup table of string:value pairs
- */
- SelectionType.prototype._findPredictions = function(arg) {
- var predictions = [];
- this.getLookup().forEach(function(item) {
- if (item.name.indexOf(arg.text) === 0) {
- predictions.push(item);
- }
- }, this);
- return predictions;
- };
-
- SelectionType.prototype.parse = function(arg) {
- var predictions = this._findPredictions(arg);
-
- if (predictions.length === 1 && predictions[0].name === arg.text) {
- var value = predictions[0].value ? predictions[0].value : predictions[0];
- return new Conversion(value, arg);
- }
-
- // This is something of a hack it basically allows us to tell the
- // setting type to forget its last setting hack.
- if (this.noMatch) {
- this.noMatch();
- }
-
- if (predictions.length > 0) {
- // Especially at startup, predictions live over the time that things
- // change so we provide a completion function rather than completion
- // values.
- // This was primarily designed for commands, which have since moved
- // into their own type, so technically we could remove this code,
- // except that it provides more up-to-date answers, and it's hard to
- // predict when it will be required.
- var predictFunc = function() {
- return this._findPredictions(arg);
- }.bind(this);
- return new Conversion(null, arg, Status.INCOMPLETE, '', predictFunc);
- }
-
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]));
- };
-
- /**
- * For selections, up is down and black is white. It's like this, given a list
- * [ a, b, c, d ], it's natural to think that it starts at the top and that
- * going up the list, moves towards 'a'. However 'a' has the lowest index, so
- * for SelectionType, up is down and down is up.
- * Sorry.
- */
- SelectionType.prototype.decrement = function(value) {
- var lookup = this.getLookup();
- var index = this._findValue(lookup, value);
- if (index === -1) {
- index = 0;
- }
- index++;
- if (index >= lookup.length) {
- index = 0;
- }
- return lookup[index].value;
- };
-
- /**
- * See note on SelectionType.decrement()
- */
- SelectionType.prototype.increment = function(value) {
- var lookup = this.getLookup();
- var index = this._findValue(lookup, value);
- if (index === -1) {
- // For an increment operation when there is nothing to start from, we
- // want to start from the top, i.e. index 0, so the value before we
- // 'increment' (see note above) must be 1.
- index = 1;
- }
- index--;
- if (index < 0) {
- index = lookup.length - 1;
- }
- return lookup[index].value;
- };
-
- /**
- * Walk through an array of { name:.., value:... } objects looking for a
- * matching value (using strict equality), returning the matched index (or -1
- * if not found).
- * @param lookup Array of objects with name/value properties to search through
- * @param value The value to search for
- * @return The index at which the match was found, or -1 if no match was found
- */
- SelectionType.prototype._findValue = function(lookup, value) {
- var index = -1;
- for (var i = 0; i < lookup.length; i++) {
- var pair = lookup[i];
- if (pair.value === value) {
- index = i;
- break;
- }
- }
- return index;
- };
-
- SelectionType.prototype.name = 'selection';
-
- exports.SelectionType = SelectionType;
-
-
- /**
- * true/false values
- */
- function BooleanType(typeSpec) {
- if (typeSpec != null) {
- throw new Error('BooleanType can not be customized');
- }
- }
-
- BooleanType.prototype = Object.create(SelectionType.prototype);
-
- BooleanType.prototype.lookup = [
- { name: 'true', value: true },
- { name: 'false', value: false }
- ];
-
- BooleanType.prototype.parse = function(arg) {
- if (arg instanceof TrueNamedArgument) {
- return new Conversion(true, arg);
- }
- if (arg instanceof FalseNamedArgument) {
- return new Conversion(false, arg);
- }
- return SelectionType.prototype.parse.call(this, arg);
- };
-
- BooleanType.prototype.stringify = function(value) {
- return '' + value;
- };
-
- BooleanType.prototype.getDefault = function() {
- return new Conversion(false, new Argument(''));
- };
-
- BooleanType.prototype.name = 'boolean';
-
- exports.BooleanType = BooleanType;
-
-
- /**
- * A type for "we don't know right now, but hope to soon".
- */
- function DeferredType(typeSpec) {
- if (typeof typeSpec.defer !== 'function') {
- throw new Error('Instances of DeferredType need typeSpec.defer to be a function that returns a type');
- }
- Object.keys(typeSpec).forEach(function(key) {
- this[key] = typeSpec[key];
- }, this);
- }
-
- DeferredType.prototype = Object.create(Type.prototype);
-
- DeferredType.prototype.stringify = function(value) {
- return this.defer().stringify(value);
- };
-
- DeferredType.prototype.parse = function(arg) {
- return this.defer().parse(arg);
- };
-
- DeferredType.prototype.decrement = function(value) {
- var deferred = this.defer();
- return (deferred.decrement ? deferred.decrement(value) : undefined);
- };
-
- DeferredType.prototype.increment = function(value) {
- var deferred = this.defer();
- return (deferred.increment ? deferred.increment(value) : undefined);
- };
-
- DeferredType.prototype.increment = function(value) {
- var deferred = this.defer();
- return (deferred.increment ? deferred.increment(value) : undefined);
- };
-
- DeferredType.prototype.name = 'deferred';
-
- exports.DeferredType = DeferredType;
-
-
- /**
- * 'blank' is a type for use with DeferredType when we don't know yet.
- * It should not be used anywhere else.
- */
- function BlankType(typeSpec) {
- if (typeSpec != null) {
- throw new Error('BlankType can not be customized');
- }
- }
-
- BlankType.prototype = Object.create(Type.prototype);
-
- BlankType.prototype.stringify = function(value) {
- return '';
- };
-
- BlankType.prototype.parse = function(arg) {
- return new Conversion(null, arg);
- };
-
- BlankType.prototype.name = 'blank';
-
- exports.BlankType = BlankType;
-
-
- /**
- * A set of objects of the same type
- */
- function ArrayType(typeSpec) {
- if (!typeSpec.subtype) {
- console.error('Array.typeSpec is missing subtype. Assuming string.' +
- JSON.stringify(typeSpec));
- typeSpec.subtype = 'string';
- }
-
- Object.keys(typeSpec).forEach(function(key) {
- this[key] = typeSpec[key];
- }, this);
- this.subtype = types.getType(this.subtype);
- }
-
- ArrayType.prototype = Object.create(Type.prototype);
-
- ArrayType.prototype.stringify = function(values) {
- // BUG 664204: Check for strings with spaces and add quotes
- return values.join(' ');
- };
-
- ArrayType.prototype.parse = function(arg) {
- if (arg instanceof ArrayArgument) {
- var conversions = arg.getArguments().map(function(subArg) {
- var conversion = this.subtype.parse(subArg);
- // Hack alert. ArrayConversion needs to be able to answer questions
- // about the status of individual conversions in addition to the
- // overall state. This allows us to do that easily.
- subArg.conversion = conversion;
- return conversion;
- }, this);
- return new ArrayConversion(conversions, arg);
- }
- else {
- console.error('non ArrayArgument to ArrayType.parse', arg);
- throw new Error('non ArrayArgument to ArrayType.parse');
- }
- };
-
- ArrayType.prototype.getDefault = function() {
- return new ArrayConversion([], new ArrayArgument());
- };
-
- ArrayType.prototype.name = 'array';
-
- exports.ArrayType = ArrayType;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/types/javascript', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types'], function(require, exports, module) {
-
-
- var l10n = require('gcli/l10n');
- var types = require('gcli/types');
-
- var Conversion = types.Conversion;
- var Type = types.Type;
- var Status = types.Status;
-
-
- /**
- * Registration and de-registration.
- */
- exports.startup = function() {
- types.registerType(JavascriptType);
- };
-
- exports.shutdown = function() {
- types.unregisterType(JavascriptType);
- };
-
- /**
- * The object against which we complete, which is usually 'window' if it exists
- * but could be something else in non-web-content environments.
- */
- var globalObject;
- if (typeof window !== 'undefined') {
- globalObject = window;
- }
-
- /**
- * Setter for the object against which JavaScript completions happen
- */
- exports.setGlobalObject = function(obj) {
- globalObject = obj;
- };
-
- /**
- * Getter for the object against which JavaScript completions happen, for use
- * in testing
- */
- exports.getGlobalObject = function() {
- return globalObject;
- };
-
- /**
- * Remove registration of object against which JavaScript completions happen
- */
- exports.unsetGlobalObject = function() {
- globalObject = undefined;
- };
-
-
- /**
- * 'javascript' handles scripted input
- */
- function JavascriptType(typeSpec) {
- if (typeSpec != null) {
- throw new Error('JavascriptType can not be customized');
- }
- }
-
- JavascriptType.prototype = Object.create(Type.prototype);
-
- JavascriptType.prototype.stringify = function(value) {
- if (value == null) {
- return '';
- }
- return value;
- };
-
- /**
- * When sorting out completions, there is no point in displaying millions of
- * matches - this the number of matches that we aim for
- */
- JavascriptType.MAX_COMPLETION_MATCHES = 10;
-
- JavascriptType.prototype.parse = function(arg) {
- var typed = arg.text;
- var scope = globalObject;
-
- // Analyze the input text and find the beginning of the last part that
- // should be completed.
- var beginning = this._findCompletionBeginning(typed);
-
- // There was an error analyzing the string.
- if (beginning.err) {
- return new Conversion(typed, arg, Status.ERROR, beginning.err);
- }
-
- // If the current state is not ParseState.NORMAL, then we are inside of a
- // string which means that no completion is possible.
- if (beginning.state !== ParseState.NORMAL) {
- return new Conversion(typed, arg, Status.INCOMPLETE, '');
- }
-
- var completionPart = typed.substring(beginning.startPos);
- var properties = completionPart.split('.');
- var matchProp;
- var prop;
-
- if (properties.length > 1) {
- matchProp = properties.pop().trimLeft();
- for (var i = 0; i < properties.length; i++) {
- prop = properties[i].trim();
-
- // We can't complete on null.foo, so bail out
- if (scope == null) {
- return new Conversion(typed, arg, Status.ERROR,
- l10n.lookup('jstypeParseScope'));
- }
-
- if (prop === '') {
- return new Conversion(typed, arg, Status.INCOMPLETE, '');
- }
-
- // Check if prop is a getter function on 'scope'. Functions can change
- // other stuff so we can't execute them to get the next object. Stop here.
- if (this._isSafeProperty(scope, prop)) {
- return new Conversion(typed, arg);
- }
-
- try {
- scope = scope[prop];
- }
- catch (ex) {
- // It would be nice to be able to report this error in some way but
- // as it can happen just when someone types '{sessionStorage.', it
- // almost doesn't really count as an error, so we ignore it
- return new Conversion(typed, arg, Status.INCOMPLETE, '');
- }
- }
- }
- else {
- matchProp = properties[0].trimLeft();
- }
-
- // If the reason we just stopped adjusting the scope was a non-simple string,
- // then we're not sure if the input is valid or invalid, so accept it
- if (prop && !prop.match(/^[0-9A-Za-z]*$/)) {
- return new Conversion(typed, arg);
- }
-
- // However if the prop was a simple string, it is an error
- if (scope == null) {
- return new Conversion(typed, arg, Status.ERROR,
- l10n.lookupFormat('jstypeParseMissing', [ prop ]));
- }
-
- // If the thing we're looking for isn't a simple string, then we're not going
- // to find it, but we're not sure if it's valid or invalid, so accept it
- if (!matchProp.match(/^[0-9A-Za-z]*$/)) {
- return new Conversion(typed, arg);
- }
-
- // Skip Iterators and Generators.
- if (this._isIteratorOrGenerator(scope)) {
- return null;
- }
-
- var matchLen = matchProp.length;
- var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen);
- var status = Status.INCOMPLETE;
- var message = '';
-
- // We really want an array of matches (for sorting) but it's easier to
- // detect existing members if we're using a map initially
- var matches = {};
-
- // We only display a maximum of MAX_COMPLETION_MATCHES, so there is no point
- // in digging up the prototype chain for matches that we're never going to
- // use. Initially look for matches directly on the object itself and then
- // look up the chain to find more
- var distUpPrototypeChain = 0;
- var root = scope;
- try {
- while (root != null &&
- Object.keys(matches).length < JavascriptType.MAX_COMPLETION_MATCHES) {
-
- Object.keys(root).forEach(function(property) {
- // Only add matching properties. Also, as we're walking up the
- // prototype chain, properties on 'higher' prototypes don't override
- // similarly named properties lower down
- if (property.indexOf(matchProp) === 0 && !(property in matches)) {
- matches[property] = {
- prop: property,
- distUpPrototypeChain: distUpPrototypeChain
- };
- }
- });
-
- distUpPrototypeChain++;
- root = Object.getPrototypeOf(root);
- }
- }
- catch (ex) {
- return new Conversion(typed, arg, Status.INCOMPLETE, '');
- }
-
- // Convert to an array for sorting, and while we're at it, note if we got
- // an exact match so we know that this input is valid
- matches = Object.keys(matches).map(function(property) {
- if (property === matchProp) {
- status = Status.VALID;
- }
- return matches[property];
- });
-
- // The sort keys are:
- // - Being on the object itself, not in the prototype chain
- // - The lack of existence of a vendor prefix
- // - The name
- matches.sort(function(m1, m2) {
- if (m1.distUpPrototypeChain !== m2.distUpPrototypeChain) {
- return m1.distUpPrototypeChain - m2.distUpPrototypeChain;
- }
- // Push all vendor prefixes to the bottom of the list
- return isVendorPrefixed(m1.prop) ?
- (isVendorPrefixed(m2.prop) ? m1.prop.localeCompare(m2.prop) : 1) :
- (isVendorPrefixed(m2.prop) ? -1 : m1.prop.localeCompare(m2.prop));
- });
-
- // Trim to size. There is a bug for doing a better job of finding matches
- // (bug 682694), but in the mean time there is a performance problem
- // associated with creating a large number of DOM nodes that few people will
- // ever read, so trim ...
- if (matches.length > JavascriptType.MAX_COMPLETION_MATCHES) {
- matches = matches.slice(0, JavascriptType.MAX_COMPLETION_MATCHES - 1);
- }
-
- // Decorate the matches with:
- // - a description
- // - a value (for the menu) and,
- // - an incomplete flag which reports if we should assume that the user isn't
- // going to carry on the JS expression with this input so far
- var predictions = matches.map(function(match) {
- var description;
- var incomplete = true;
-
- if (this._isSafeProperty(scope, match.prop)) {
- description = '(property getter)';
- }
- else {
- try {
- var value = scope[match.prop];
-
- if (typeof value === 'function') {
- description = '(function)';
- }
- else if (typeof value === 'boolean' || typeof value === 'number') {
- description = '= ' + value;
- incomplete = false;
- }
- else if (typeof value === 'string') {
- if (value.length > 40) {
- value = value.substring(0, 37) + '…';
- }
- description = '= \'' + value + '\'';
- incomplete = false;
- }
- else {
- description = '(' + typeof value + ')';
- }
- }
- catch (ex) {
- description = '(' + l10n.lookup('jstypeParseError') + ')';
- }
- }
-
- return {
- name: prefix + match.prop,
- value: {
- name: prefix + match.prop,
- description: description
- },
- description: description,
- incomplete: incomplete
- };
- }, this);
-
- if (predictions.length === 0) {
- status = Status.ERROR;
- message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]);
- }
-
- // If the match is the only one possible, and its VALID, predict nothing
- if (predictions.length === 1 && status === Status.VALID) {
- predictions = undefined;
- }
-
- return new Conversion(typed, arg, status, message, predictions);
- };
-
- /**
- * Does the given property have a prefix that indicates that it is vendor
- * specific?
- */
- function isVendorPrefixed(name) {
- return name.indexOf('moz') === 0 ||
- name.indexOf('webkit') === 0 ||
- name.indexOf('ms') === 0;
- }
-
- /**
- * Constants used in return value of _findCompletionBeginning()
- */
- var ParseState = {
- NORMAL: 0,
- QUOTE: 2,
- DQUOTE: 3
- };
-
- var OPEN_BODY = '{[('.split('');
- var CLOSE_BODY = '}])'.split('');
- var OPEN_CLOSE_BODY = {
- '{': '}',
- '[': ']',
- '(': ')'
- };
-
- /**
- * Analyzes a given string to find the last statement that is interesting for
- * later completion.
- * @param text A string to analyze
- * @return If there was an error in the string detected, then a object like
- * { err: 'ErrorMesssage' }
- * is returned, otherwise a object like
- * {
- * state: ParseState.NORMAL|ParseState.QUOTE|ParseState.DQUOTE,
- * startPos: index of where the last statement begins
- * }
- */
- JavascriptType.prototype._findCompletionBeginning = function(text) {
- var bodyStack = [];
-
- var state = ParseState.NORMAL;
- var start = 0;
- var c;
- for (var i = 0; i < text.length; i++) {
- c = text[i];
-
- switch (state) {
- // Normal JS state.
- case ParseState.NORMAL:
- if (c === '"') {
- state = ParseState.DQUOTE;
- }
- else if (c === '\'') {
- state = ParseState.QUOTE;
- }
- else if (c === ';') {
- start = i + 1;
- }
- else if (c === ' ') {
- start = i + 1;
- }
- else if (OPEN_BODY.indexOf(c) != -1) {
- bodyStack.push({
- token: c,
- start: start
- });
- start = i + 1;
- }
- else if (CLOSE_BODY.indexOf(c) != -1) {
- var last = bodyStack.pop();
- if (!last || OPEN_CLOSE_BODY[last.token] != c) {
- return { err: l10n.lookup('jstypeBeginSyntax') };
- }
- if (c === '}') {
- start = i + 1;
- }
- else {
- start = last.start;
- }
- }
- break;
-
- // Double quote state > " <
- case ParseState.DQUOTE:
- if (c === '\\') {
- i ++;
- }
- else if (c === '\n') {
- return { err: l10n.lookup('jstypeBeginUnterm') };
- }
- else if (c === '"') {
- state = ParseState.NORMAL;
- }
- break;
-
- // Single quote state > ' <
- case ParseState.QUOTE:
- if (c === '\\') {
- i ++;
- }
- else if (c === '\n') {
- return { err: l10n.lookup('jstypeBeginUnterm') };
- }
- else if (c === '\'') {
- state = ParseState.NORMAL;
- }
- break;
- }
- }
-
- return {
- state: state,
- startPos: start
- };
- };
-
- /**
- * Return true if the passed object is either an iterator or a generator, and
- * false otherwise
- * @param obj The object to check
- */
- JavascriptType.prototype._isIteratorOrGenerator = function(obj) {
- if (obj === null) {
- return false;
- }
-
- if (typeof aObject === 'object') {
- if (typeof obj.__iterator__ === 'function' ||
- obj.constructor && obj.constructor.name === 'Iterator') {
- return true;
- }
-
- try {
- var str = obj.toString();
- if (typeof obj.next === 'function' &&
- str.indexOf('[object Generator') === 0) {
- return true;
- }
- }
- catch (ex) {
- // window.history.next throws in the typeof check above.
- return false;
- }
- }
-
- return false;
- };
-
- /**
- * Would calling 'scope[prop]' cause the invocation of a non-native (i.e. user
- * defined) function property?
- * Since calling functions can have side effects, it's only safe to do that if
- * explicitly requested, rather than because we're trying things out for the
- * purposes of completion.
- */
- JavascriptType.prototype._isSafeProperty = function(scope, prop) {
- if (typeof scope !== 'object') {
- return false;
- }
-
- // Walk up the prototype chain of 'scope' looking for a property descriptor
- // for 'prop'
- var propDesc;
- while (scope) {
- try {
- propDesc = Object.getOwnPropertyDescriptor(scope, prop);
- if (propDesc) {
- break;
- }
- }
- catch (ex) {
- // Native getters throw here. See bug 520882.
- if (ex.name === 'NS_ERROR_XPC_BAD_CONVERT_JS' ||
- ex.name === 'NS_ERROR_XPC_BAD_OP_ON_WN_PROTO') {
- return false;
- }
- return true;
- }
- scope = Object.getPrototypeOf(scope);
- }
-
- if (!propDesc) {
- return false;
- }
-
- if (!propDesc.get) {
- return false;
- }
-
- // The property is safe if 'get' isn't a function or if the function has a
- // prototype (in which case it's native)
- return typeof propDesc.get !== 'function' || 'prototype' in propDesc.get;
- };
-
- JavascriptType.prototype.name = 'javascript';
-
- exports.JavascriptType = JavascriptType;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/types/node', ['require', 'exports', 'module' , 'gcli/host', 'gcli/l10n', 'gcli/types'], function(require, exports, module) {
-
-
- var host = require('gcli/host');
- var l10n = require('gcli/l10n');
- var types = require('gcli/types');
- var Type = require('gcli/types').Type;
- var Status = require('gcli/types').Status;
- var Conversion = require('gcli/types').Conversion;
-
-
- /**
- * Registration and de-registration.
- */
- exports.startup = function() {
- types.registerType(NodeType);
- };
-
- exports.shutdown = function() {
- types.unregisterType(NodeType);
- };
-
- /**
- * The object against which we complete, which is usually 'window' if it exists
- * but could be something else in non-web-content environments.
- */
- var doc;
- if (typeof document !== 'undefined') {
- doc = document;
- }
-
- /**
- * Setter for the document that contains the nodes we're matching
- */
- exports.setDocument = function(document) {
- doc = document;
- };
-
- /**
- * Undo the effects of setDocument()
- */
- exports.unsetDocument = function() {
- doc = undefined;
- };
-
-
- /**
- * A CSS expression that refers to a single node
- */
- function NodeType(typeSpec) {
- if (typeSpec != null) {
- throw new Error('NodeType can not be customized');
- }
- }
-
- NodeType.prototype = Object.create(Type.prototype);
-
- NodeType.prototype.stringify = function(value) {
- return value.__gcliQuery || 'Error';
- };
-
- NodeType.prototype.parse = function(arg) {
- if (arg.text === '') {
- return new Conversion(null, arg, Status.INCOMPLETE,
- l10n.lookup('nodeParseNone'));
- }
-
- var nodes;
- try {
- nodes = doc.querySelectorAll(arg.text);
- }
- catch (ex) {
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookup('nodeParseSyntax'));
- }
-
- if (nodes.length === 0) {
- return new Conversion(null, arg, Status.INCOMPLETE,
- l10n.lookup('nodeParseNone'));
- }
-
- if (nodes.length === 1) {
- var node = nodes.item(0);
- node.__gcliQuery = arg.text;
-
- host.flashNode(node, 'green');
-
- return new Conversion(node, arg, Status.VALID, '');
- }
-
- Array.prototype.forEach.call(nodes, function(n) {
- host.flashNode(n, 'red');
- });
-
- return new Conversion(null, arg, Status.ERROR,
- l10n.lookupFormat('nodeParseMultiple', [ nodes.length ]));
- };
-
- NodeType.prototype.name = 'node';
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/host', ['require', 'exports', 'module' ], function(require, exports, module) {
-
-
- /**
- * Helper to turn a node background it's complementary color for 1 second.
- * There is likely a better way to do this, but this will do for now.
- */
- exports.flashNode = function(node, color) {
- if (!node.__gcliHighlighting) {
- node.__gcliHighlighting = true;
- var original = node.style.background;
- node.style.background = color;
- setTimeout(function() {
- node.style.background = original;
- delete node.__gcliHighlighting;
- }, 1000);
- }
- };
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
-
-
- var util = require('gcli/util');
-
- var canon = require('gcli/canon');
- var Promise = require('gcli/promise').Promise;
-
- var types = require('gcli/types');
- var Status = require('gcli/types').Status;
- var Conversion = require('gcli/types').Conversion;
- var Type = require('gcli/types').Type;
- var ArrayType = require('gcli/types/basic').ArrayType;
- var StringType = require('gcli/types/basic').StringType;
- var BooleanType = require('gcli/types/basic').BooleanType;
- var SelectionType = require('gcli/types/basic').SelectionType;
-
- var Argument = require('gcli/argument').Argument;
- var ArrayArgument = require('gcli/argument').ArrayArgument;
- var NamedArgument = require('gcli/argument').NamedArgument;
- var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
- var MergedArgument = require('gcli/argument').MergedArgument;
- var ScriptArgument = require('gcli/argument').ScriptArgument;
-
- var evalCommand;
-
- /**
- * Registration and de-registration.
- */
- exports.startup = function() {
- types.registerType(CommandType);
- evalCommand = canon.addCommand(evalCommandSpec);
- };
-
- exports.shutdown = function() {
- types.unregisterType(CommandType);
- canon.removeCommand(evalCommandSpec.name);
- evalCommand = undefined;
- };
-
-
- /**
- * Assignment is a link between a parameter and the data for that parameter.
- * The data for the parameter is available as in the preferred type and as
- * an Argument for the CLI.
- * <p>We also record validity information where applicable.
- * <p>For values, null and undefined have distinct definitions. null means
- * that a value has been provided, undefined means that it has not.
- * Thus, null is a valid default value, and common because it identifies an
- * parameter that is optional. undefined means there is no value from
- * the command line.
- *
- * <h2>Events<h2>
- * Assignment publishes the following event:<ul>
- * <li>assignmentChange: Either the value or the text has changed. It is likely
- * that any UI component displaying this argument will need to be updated.
- * The event object looks like:
- * <tt>{ assignment: ..., conversion: ..., oldConversion: ... }</tt>
- * @constructor
- */
- function Assignment(param, paramIndex) {
- this.param = param;
- this.paramIndex = paramIndex;
- this.assignmentChange = util.createEvent('Assignment.assignmentChange');
-
- this.setDefault();
- }
-
- /**
- * The parameter that we are assigning to
- * @readonly
- */
- Assignment.prototype.param = undefined;
-
- Assignment.prototype.conversion = undefined;
-
- /**
- * The index of this parameter in the parent Requisition. paramIndex === -1
- * is the command assignment although this should not be relied upon, it is
- * better to test param instanceof CommandAssignment
- */
- Assignment.prototype.paramIndex = undefined;
-
- /**
- * Easy accessor for conversion.arg
- */
- Assignment.prototype.getArg = function() {
- return this.conversion.arg;
- };
-
- /**
- * Easy accessor for conversion.value
- */
- Assignment.prototype.getValue = function() {
- return this.conversion.value;
- };
-
- /**
- * Easy (and safe) accessor for conversion.message
- */
- Assignment.prototype.getMessage = function() {
- return this.conversion.message ? this.conversion.message : '';
- };
-
- /**
- * Easy (and safe) accessor for conversion.getPredictions()
- * @return An array of objects with name and value elements. For example:
- * [ { name:'bestmatch', value:foo1 }, { name:'next', value:foo2 }, ... ]
- */
- Assignment.prototype.getPredictions = function() {
- return this.conversion.getPredictions();
- };
-
- /**
- * Report on the status of the last parse() conversion.
- * We force mutations to happen through this method rather than have
- * setValue and setArgument functions to help maintain integrity when we
- * have ArrayArguments and don't want to get confused. This way assignments
- * are just containers for a conversion rather than things that store
- * a connection between an arg/value.
- * @see types.Conversion
- */
- Assignment.prototype.setConversion = function(conversion) {
- var oldConversion = this.conversion;
-
- this.conversion = conversion;
- this.conversion.assign(this);
-
- if (this.conversion.equals(oldConversion)) {
- return;
- }
-
- this.assignmentChange({
- assignment: this,
- conversion: this.conversion,
- oldConversion: oldConversion
- });
- };
-
- /**
- * Find a default value for the conversion either from the parameter, or from
- * the type, or failing that by parsing an empty argument.
- */
- Assignment.prototype.setDefault = function() {
- var conversion;
- if (this.param.getDefault) {
- conversion = this.param.getDefault();
- }
- else if (this.param.type.getDefault) {
- conversion = this.param.type.getDefault();
- }
- else {
- conversion = this.param.type.parse(new Argument());
- }
-
- this.setConversion(conversion);
- };
-
- /**
- * Make sure that there is some content for this argument by using an
- * Argument of '' if needed.
- */
- Assignment.prototype.ensureVisibleArgument = function() {
- // It isn't clear if we should be sending events from this method.
- // It should only be called when structural changes are happening in which
- // case we're going to ignore the event anyway. But on the other hand
- // perhaps this function shouldn't need to know how it is used, and should
- // do the inefficient thing.
- if (!this.conversion.arg.isBlank()) {
- return false;
- }
-
- var arg = this.conversion.arg.beget('', {
- prefixSpace: this.param instanceof CommandAssignment
- });
- this.conversion = this.param.type.parse(arg);
- this.conversion.assign(this);
-
- return true;
- };
-
- /**
- * Work out what the status of the current conversion is which involves looking
- * not only at the conversion, but also checking if data has been provided
- * where it should.
- * @param arg For assignments with multiple args (e.g. array assignments) we
- * can narrow the search for status to a single argument.
- */
- Assignment.prototype.getStatus = function(arg) {
- if (this.param.isDataRequired() && !this.conversion.isDataProvided()) {
- return Status.ERROR;
- }
-
- // Selection/Boolean types with a defined range of values will say that
- // '' is INCOMPLETE, but the parameter may be optional, so we don't ask
- // if the user doesn't need to enter something and hasn't done so.
- if (!this.param.isDataRequired() && this.getArg().isBlank()) {
- return Status.VALID;
- }
-
- return this.conversion.getStatus(arg);
- };
-
- /**
- * Basically <tt>value = conversion.predictions[0])</tt> done in a safe way.
- */
- Assignment.prototype.complete = function() {
- var predictions = this.conversion.getPredictions();
- if (predictions.length > 0) {
- var arg = this.conversion.arg.beget(predictions[0].name);
- if (!predictions[0].incomplete) {
- arg.suffix = ' ';
- }
- var conversion = this.param.type.parse(arg);
- this.setConversion(conversion);
- }
- };
-
- /**
- * Replace the current value with the lower value if such a concept exists.
- */
- Assignment.prototype.decrement = function() {
- var replacement = this.param.type.decrement(this.conversion.value);
- if (replacement != null) {
- var str = this.param.type.stringify(replacement);
- var arg = this.conversion.arg.beget(str);
- var conversion = new Conversion(replacement, arg);
- this.setConversion(conversion);
- }
- };
-
- /**
- * Replace the current value with the higher value if such a concept exists.
- */
- Assignment.prototype.increment = function() {
- var replacement = this.param.type.increment(this.conversion.value);
- if (replacement != null) {
- var str = this.param.type.stringify(replacement);
- var arg = this.conversion.arg.beget(str);
- var conversion = new Conversion(replacement, arg);
- this.setConversion(conversion);
- }
- };
-
- /**
- * Helper when we're rebuilding command lines.
- */
- Assignment.prototype.toString = function() {
- return this.conversion.toString();
- };
-
- exports.Assignment = Assignment;
-
-
- /**
- * Select from the available commands.
- * This is very similar to a SelectionType, however the level of hackery in
- * SelectionType to make it handle Commands correctly was to high, so we
- * simplified.
- * If you are making changes to this code, you should check there too.
- */
- function CommandType() {
- }
-
- CommandType.prototype = Object.create(Type.prototype);
-
- CommandType.prototype.name = 'command';
-
- CommandType.prototype.decrement = SelectionType.prototype.decrement;
- CommandType.prototype.increment = SelectionType.prototype.increment;
- CommandType.prototype._findValue = SelectionType.prototype._findValue;
-
- CommandType.prototype.stringify = function(command) {
- return command.name;
- };
-
- /**
- * Trim a list of commands (as from canon.getCommands()) according to those
- * that match the provided arg.
- */
- CommandType.prototype._findPredictions = function(arg) {
- var predictions = [];
- canon.getCommands().forEach(function(command) {
- if (command.name.indexOf(arg.text) === 0) {
- // The command type needs to exclude sub-commands when the CLI
- // is blank, but include them when we're filtering. This hack
- // excludes matches when the filter text is '' and when the
- // name includes a space.
- if (arg.text.length !== 0 || command.name.indexOf(' ') === -1) {
- predictions.push(command);
- }
- }
- }, this);
- return predictions;
- };
-
- CommandType.prototype.parse = function(arg) {
- // Especially at startup, predictions live over the time that things change
- // so we provide a completion function rather than completion values
- var predictFunc = function() {
- return this._findPredictions(arg);
- }.bind(this);
-
- var predictions = this._findPredictions(arg);
-
- if (predictions.length === 0) {
- return new Conversion(null, arg, Status.ERROR,
- 'Can\'t use \'' + arg.text + '\'.', predictFunc);
- }
-
- var command = predictions[0];
-
- if (predictions.length === 1) {
- // Is it an exact match of an executable command,
- // or just the only possibility?
- if (command.name === arg.text && typeof command.exec === 'function') {
- return new Conversion(command, arg, Status.VALID, '');
- }
- return new Conversion(null, arg, Status.INCOMPLETE, '', predictFunc);
- }
-
- // It's valid if the text matches, even if there are several options
- if (command.name === arg.text) {
- return new Conversion(command, arg, Status.VALID, '', predictFunc);
- }
-
- return new Conversion(null, arg, Status.INCOMPLETE, '', predictFunc);
- };
-
-
- /**
- * How to dynamically execute JavaScript code
- */
- var customEval = eval;
-
- /**
- * Setup a function to be called in place of 'eval', generally for security
- * reasons
- */
- exports.setEvalFunction = function(newCustomEval) {
- customEval = newCustomEval;
- };
-
- /**
- * Remove the binding done by setEvalFunction().
- * We purposely set customEval to undefined rather than to 'eval' because there
- * is an implication of setEvalFunction that we're in a security sensitive
- * situation. What if we can trick GCLI into calling unsetEvalFunction() at the
- * wrong time?
- * So to properly undo the effects of setEvalFunction(), you need to call
- * setEvalFunction(eval) rather than unsetEvalFunction(), however the latter is
- * preferred in most cases.
- */
- exports.unsetEvalFunction = function() {
- customEval = undefined;
- };
-
- /**
- * 'eval' command
- */
- var evalCommandSpec = {
- name: '{',
- params: [
- {
- name: 'javascript',
- type: 'javascript',
- description: ''
- }
- ],
- returnType: 'html',
- description: { key: 'cliEvalJavascript' },
- exec: function(args, context) {
- // → is right arrow. We use explicit entities to ensure XML validity
- var resultPrefix = '<em>{ ' + args.javascript + ' }</em> → ';
- try {
- var result = customEval(args.javascript);
-
- if (result === null) {
- return resultPrefix + 'null.';
- }
-
- if (result === undefined) {
- return resultPrefix + 'undefined.';
- }
-
- if (typeof result === 'function') {
- // is
- return resultPrefix +
- (result + '').replace(/\n/g, '<br>').replace(/ /g, ' ');
- }
-
- return resultPrefix + result;
- }
- catch (ex) {
- return resultPrefix + 'Exception: ' + ex.message;
- }
- }
- };
-
-
- /**
- * This is a special assignment to reflect the command itself.
- */
- function CommandAssignment() {
- this.param = new canon.Parameter({
- name: '__command',
- type: 'command',
- description: 'The command to execute'
- });
- this.paramIndex = -1;
- this.assignmentChange = util.createEvent('CommandAssignment.assignmentChange');
-
- this.setDefault();
- }
-
- CommandAssignment.prototype = Object.create(Assignment.prototype);
-
- CommandAssignment.prototype.getStatus = function(arg) {
- return Status.combine(
- Assignment.prototype.getStatus.call(this, arg),
- this.conversion.value && !this.conversion.value.exec ?
- Status.INCOMPLETE : Status.VALID
- );
- };
-
-
- /**
- * Special assignment used when ignoring parameters that don't have a home
- */
- function UnassignedAssignment() {
- this.param = new canon.Parameter({
- name: '__unassigned',
- type: 'string'
- });
- this.paramIndex = -1;
- this.assignmentChange = util.createEvent('UnassignedAssignment.assignmentChange');
-
- this.setDefault();
- }
-
- UnassignedAssignment.prototype = Object.create(Assignment.prototype);
-
- UnassignedAssignment.prototype.getStatus = function(arg) {
- return Status.ERROR;
- };
-
- UnassignedAssignment.prototype.setUnassigned = function(args) {
- if (!args || args.length === 0) {
- this.setDefault();
- }
- else {
- var conversion = this.param.type.parse(new MergedArgument(args));
- this.setConversion(conversion);
- }
- };
-
-
- /**
- * A Requisition collects the information needed to execute a command.
- *
- * (For a definition of the term, see http://en.wikipedia.org/wiki/Requisition)
- * This term is used because carries the notion of a work-flow, or process to
- * getting the information to execute a command correct.
- * There is little point in a requisition for parameter-less commands because
- * there is no information to collect. A Requisition is a collection of
- * assignments of values to parameters, each handled by an instance of
- * Assignment.
- *
- * <h2>Events<h2>
- * <p>Requisition publishes the following events:
- * <ul>
- * <li>commandChange: The command has changed. It is likely that a UI
- * structure will need updating to match the parameters of the new command.
- * The event object looks like { command: A }
- * <li>assignmentChange: This is a forward of the Assignment.assignmentChange
- * event. It is fired when any assignment (except the commandAssignment)
- * changes.
- * <li>inputChange: The text to be mirrored in a command line has changed.
- * The event object looks like { newText: X }.
- * </ul>
- *
- * @param environment An opaque object passed to commands using ExecutionContext
- * @param document A DOM Document passed to commands using ExecutionContext in
- * order to allow creation of DOM nodes.
- * @constructor
- */
- function Requisition(environment, document) {
- this.environment = environment;
- this.document = document;
-
- // The command that we are about to execute.
- // @see setCommandConversion()
- this.commandAssignment = new CommandAssignment();
-
- // The object that stores of Assignment objects that we are filling out.
- // The Assignment objects are stored under their param.name for named
- // lookup. Note: We make use of the property of Javascript objects that
- // they are not just hashmaps, but linked-list hashmaps which iterate in
- // insertion order.
- // _assignments excludes the commandAssignment.
- this._assignments = {};
-
- // The count of assignments. Excludes the commandAssignment
- this.assignmentCount = 0;
-
- // Used to store cli arguments in the order entered on the cli
- this._args = [];
-
- // Used to store cli arguments that were not assigned to parameters
- this._unassigned = new UnassignedAssignment();
-
- // Temporarily set this to true to prevent _onAssignmentChange resetting
- // argument positions
- this._structuralChangeInProgress = false;
-
- this.commandAssignment.assignmentChange.add(this._onCommandAssignmentChange, this);
- this.commandAssignment.assignmentChange.add(this._onAssignmentChange, this);
-
- this.commandOutputManager = canon.commandOutputManager;
-
- this.assignmentChange = util.createEvent('Requisition.assignmentChange');
- this.commandChange = util.createEvent('Requisition.commandChange');
- this.inputChange = util.createEvent('Requisition.inputChange');
- }
-
- /**
- * Some number that is higher than the most args we'll ever have. Would use
- * MAX_INTEGER if that made sense
- */
- var MORE_THAN_THE_MOST_ARGS_POSSIBLE = 1000000;
-
- /**
- * Avoid memory leaks
- */
- Requisition.prototype.destroy = function() {
- this.commandAssignment.assignmentChange.remove(this._onCommandAssignmentChange, this);
- this.commandAssignment.assignmentChange.remove(this._onAssignmentChange, this);
-
- delete this.document;
- delete this.environment;
- };
-
- /**
- * When any assignment changes, we might need to update the _args array to
- * match and inform people of changes to the typed input text.
- */
- Requisition.prototype._onAssignmentChange = function(ev) {
- // Don't report an event if the value is unchanged
- if (ev.oldConversion != null &&
- ev.conversion.valueEquals(ev.oldConversion)) {
- return;
- }
-
- if (this._structuralChangeInProgress) {
- return;
- }
-
- this.assignmentChange(ev);
-
- // Both for argument position and the inputChange event, we only care
- // about changes to the argument.
- if (ev.conversion.argEquals(ev.oldConversion)) {
- return;
- }
-
- this._structuralChangeInProgress = true;
-
- // Refactor? See bug 660765
- // Do preceding arguments need to have dummy values applied so we don't
- // get a hole in the command line?
- if (ev.assignment.param.isPositionalAllowed()) {
- for (var i = 0; i < ev.assignment.paramIndex; i++) {
- var assignment = this.getAssignment(i);
- if (assignment.param.isPositionalAllowed()) {
- if (assignment.ensureVisibleArgument()) {
- this._args.push(assignment.getArg());
- }
- }
- }
- }
-
- // Remember where we found the first match
- var index = MORE_THAN_THE_MOST_ARGS_POSSIBLE;
- for (var i = 0; i < this._args.length; i++) {
- if (this._args[i].assignment === ev.assignment) {
- if (i < index) {
- index = i;
- }
- this._args.splice(i, 1);
- i--;
- }
- }
-
- if (index === MORE_THAN_THE_MOST_ARGS_POSSIBLE) {
- this._args.push(ev.assignment.getArg());
- }
- else {
- // Is there a way to do this that doesn't involve a loop?
- var newArgs = ev.conversion.arg.getArgs();
- for (var i = 0; i < newArgs.length; i++) {
- this._args.splice(index + i, 0, newArgs[i]);
- }
- }
- this._structuralChangeInProgress = false;
-
- this.inputChange();
- };
-
- /**
- * When the command changes, we need to keep a bunch of stuff in sync
- */
- Requisition.prototype._onCommandAssignmentChange = function(ev) {
- this._assignments = {};
-
- var command = this.commandAssignment.getValue();
- if (command) {
- for (var i = 0; i < command.params.length; i++) {
- var param = command.params[i];
- var assignment = new Assignment(param, i);
- assignment.assignmentChange.add(this._onAssignmentChange, this);
- this._assignments[param.name] = assignment;
- }
- }
- this.assignmentCount = Object.keys(this._assignments).length;
-
- this.commandChange({
- requisition: this,
- oldValue: ev.oldValue,
- newValue: command
- });
- // this.inputChange();
- };
-
- /**
- * Assignments have an order, so we need to store them in an array.
- * But we also need named access ...
- * @return The found assignment, or undefined, if no match was found
- */
- Requisition.prototype.getAssignment = function(nameOrNumber) {
- var name = (typeof nameOrNumber === 'string') ?
- nameOrNumber :
- Object.keys(this._assignments)[nameOrNumber];
- return this._assignments[name] || undefined;
- },
-
- /**
- * Where parameter name == assignment names - they are the same
- */
- Requisition.prototype.getParameterNames = function() {
- return Object.keys(this._assignments);
- },
-
- /**
- * A *shallow* clone of the assignments.
- * This is useful for systems that wish to go over all the assignments
- * finding values one way or another and wish to trim an array as they go.
- */
- Requisition.prototype.cloneAssignments = function() {
- return Object.keys(this._assignments).map(function(name) {
- return this._assignments[name];
- }, this);
- };
-
- /**
- * The overall status is the most severe status.
- * There is no such thing as an INCOMPLETE overall status because the
- * definition of INCOMPLETE takes into account the cursor position to say 'this
- * isn't quite ERROR because the user can fix it by typing', however overall,
- * this is still an error status.
- */
- Requisition.prototype.getStatus = function() {
- var status = Status.VALID;
- this.getAssignments(true).forEach(function(assignment) {
- var assignStatus = assignment.getStatus();
- if (assignment.getStatus() > status) {
- status = assignStatus;
- }
- }, this);
- if (status === Status.INCOMPLETE) {
- status = Status.ERROR;
- }
- return status;
- };
-
- /**
- * Extract the names and values of all the assignments, and return as
- * an object.
- */
- Requisition.prototype.getArgsObject = function() {
- var args = {};
- this.getAssignments().forEach(function(assignment) {
- args[assignment.param.name] = assignment.getValue();
- }, this);
- return args;
- };
-
- /**
- * Access the arguments as an array.
- * @param includeCommand By default only the parameter arguments are
- * returned unless (includeCommand === true), in which case the list is
- * prepended with commandAssignment.getArg()
- */
- Requisition.prototype.getAssignments = function(includeCommand) {
- var assignments = [];
- if (includeCommand === true) {
- assignments.push(this.commandAssignment);
- }
- Object.keys(this._assignments).forEach(function(name) {
- assignments.push(this.getAssignment(name));
- }, this);
- return assignments;
- };
-
- /**
- * Reset all the assignments to their default values
- */
- Requisition.prototype.setDefaultArguments = function() {
- this.getAssignments().forEach(function(assignment) {
- assignment.setDefault();
- }, this);
- };
-
- /**
- * Extract a canonical version of the input
- */
- Requisition.prototype.toCanonicalString = function() {
- var line = [];
-
- var cmd = this.commandAssignment.getValue() ?
- this.commandAssignment.getValue().name :
- this.commandAssignment.getArg().text;
- line.push(cmd);
-
- Object.keys(this._assignments).forEach(function(name) {
- var assignment = this._assignments[name];
- var type = assignment.param.type;
- // Bug 664377: This will cause problems if there is a non-default value
- // after a default value. Also we need to decide when to use
- // named parameters in place of positional params. Both can wait.
- if (assignment.getValue() !== assignment.param.defaultValue) {
- line.push(' ');
- line.push(type.stringify(assignment.getValue()));
- }
- }, this);
-
- // Canonically, if we've opened with a { then we should have a } to close
- var command = this.commandAssignment.getValue();
- if (cmd === '{') {
- if (this.getAssignment(0).getArg().suffix.indexOf('}') === -1) {
- line.push(' }');
- }
- }
-
- return line.join('');
- };
-
- /**
- * Input trace gives us an array of Argument tracing objects, one for each
- * character in the typed input, from which we can derive information about how
- * to display this typed input. It's a bit like toString on steroids.
- * <p>
- * The returned object has the following members:<ul>
- * <li>char: The character to which this arg trace refers.
- * <li>arg: The Argument to which this character is assigned.
- * <li>part: One of ['prefix'|'text'|suffix'] - how was this char understood
- * </ul>
- * <p>
- * The Argument objects are as output from #_tokenize() rather than as applied
- * to Assignments by #_assign() (i.e. they are not instances of NamedArgument,
- * ArrayArgument, etc).
- * <p>
- * To get at the arguments applied to the assignments simply call
- * <tt>arg.assignment.arg</tt>. If <tt>arg.assignment.arg !== arg</tt> then
- * the arg applied to the assignment will contain the original arg.
- * See #_assign() for details.
- */
- Requisition.prototype.createInputArgTrace = function() {
- if (!this._args) {
- throw new Error('createInputMap requires a command line. See source.');
- // If this is a problem then we can fake command line input using
- // something like the code in #toCanonicalString().
- }
-
- var args = [];
- this._args.forEach(function(arg) {
- for (var i = 0; i < arg.prefix.length; i++) {
- args.push({ arg: arg, char: arg.prefix[i], part: 'prefix' });
- }
- for (var i = 0; i < arg.text.length; i++) {
- args.push({ arg: arg, char: arg.text[i], part: 'text' });
- }
- for (var i = 0; i < arg.suffix.length; i++) {
- args.push({ arg: arg, char: arg.suffix[i], part: 'suffix' });
- }
- });
-
- return args;
- };
-
- /**
- * Reconstitute the input from the args
- */
- Requisition.prototype.toString = function() {
- if (this._args) {
- return this._args.map(function(arg) {
- return arg.toString();
- }).join('');
- }
-
- return this.toCanonicalString();
- };
-
- /**
- * Return an array of Status scores so we can create a marked up
- * version of the command line input.
- */
- Requisition.prototype.getInputStatusMarkup = function() {
- var argTraces = this.createInputArgTrace();
- // We only take a status of INCOMPLETE to be INCOMPLETE when the cursor is
- // actually in the argument. Otherwise it's an error.
- // Generally the 'argument at the cursor' is the argument before the cursor
- // unless it is before the first char, in which case we take the first.
- var cursor = this.input.cursor.start === 0 ?
- 0 :
- this.input.cursor.start - 1;
- var cTrace = argTraces[cursor];
-
- var statuses = [];
- for (var i = 0; i < argTraces.length; i++) {
- var argTrace = argTraces[i];
- var arg = argTrace.arg;
- var status = Status.VALID;
- if (argTrace.part === 'text') {
- status = arg.assignment.getStatus(arg);
- // Promote INCOMPLETE to ERROR ...
- if (status === Status.INCOMPLETE) {
- // If the cursor is not in a position to be able to complete it
- if (arg !== cTrace.arg || cTrace.part !== 'text') {
- // And if we're not in the command
- if (!(arg.assignment instanceof CommandAssignment)) {
- status = Status.ERROR;
- }
- }
- }
- }
-
- statuses.push(status);
- }
-
- return statuses;
- };
-
- /**
- * Look through the arguments attached to our assignments for the assignment
- * at the given position.
- * @param {number} cursor The cursor position to query
- */
- Requisition.prototype.getAssignmentAt = function(cursor) {
- if (!this._args) {
- console.trace();
- throw new Error('Missing args');
- }
-
- // We short circuit this one because we may have no args, or no args with
- // any size and the alg below only finds arguments with size.
- if (cursor === 0) {
- return this.commandAssignment;
- }
-
- var assignForPos = [];
- var i, j;
- for (i = 0; i < this._args.length; i++) {
- var arg = this._args[i];
- var assignment = arg.assignment;
-
- // prefix and text are clearly part of the argument
- for (j = 0; j < arg.prefix.length; j++) {
- assignForPos.push(assignment);
- }
- for (j = 0; j < arg.text.length; j++) {
- assignForPos.push(assignment);
- }
-
- // suffix looks forwards
- if (this._args.length > i + 1) {
- // first to the next argument
- assignment = this._args[i + 1].assignment;
- }
- else if (assignment &&
- assignment.paramIndex + 1 < this.assignmentCount) {
- // then to the next assignment
- assignment = this.getAssignment(assignment.paramIndex + 1);
- }
-
- for (j = 0; j < arg.suffix.length; j++) {
- assignForPos.push(assignment);
- }
- }
-
- // Possible shortcut, we don't really need to go through all the args
- // to work out the solution to this
-
- var reply = assignForPos[cursor - 1];
-
- if (!reply) {
- throw new Error('Missing assignment.' +
- ' cursor=' + cursor + ' text.length=' + this.toString().length);
- }
-
- return reply;
- };
-
- /**
- * Entry point for keyboard accelerators or anything else that wants to execute
- * a command.
- * @param input Object containing data about how to execute the command.
- * Properties of input include:
- * - args: Arguments for the command
- * - typed: The typed command
- * - visible: Ensure that the output from this command is visible
- */
- Requisition.prototype.exec = function(input) {
- var command;
- var args;
- var visible = true;
-
- if (input) {
- if (input.args != null) {
- // Fast track by looking up the command directly since passed args
- // means there is no command line to parse.
- command = canon.getCommand(input.typed);
- if (!command) {
- console.error('Command not found: ' + command);
- }
- args = input.args;
-
- // Default visible to false since this is exec is probably the
- // result of a keyboard shortcut
- visible = 'visible' in input ? input.visible : false;
- }
- else {
- this.update(input);
- }
- }
-
- if (!command) {
- command = this.commandAssignment.getValue();
- args = this.getArgsObject();
- }
-
- if (!command) {
- return false;
- }
-
- var outputObject = {
- command: command,
- args: args,
- typed: this.toCanonicalString(),
- completed: false,
- start: new Date()
- };
-
- this.commandOutputManager.sendCommandOutput(outputObject);
-
- var onComplete = (function(output, error) {
- if (visible) {
- outputObject.end = new Date();
- outputObject.duration = outputObject.end.getTime() - outputObject.start.getTime();
- outputObject.error = error;
- outputObject.output = output;
- outputObject.completed = true;
- this.commandOutputManager.sendCommandOutput(outputObject);
- }
- }).bind(this);
-
- try {
- var context = new ExecutionContext(this.environment, this.document);
- var reply = command.exec(args, context);
-
- if (reply != null && reply.isPromise) {
- reply.then(
- function(data) { onComplete(data, false); },
- function(error) { onComplete(error, true); });
-
- // Add progress to our promise and add a handler for it here
- // See bug 659300
- }
- else {
- onComplete(reply, false);
- }
- }
- catch (ex) {
- onComplete(ex, true);
- }
-
- this.clear();
- return true;
- };
-
- /**
- * Called by the UI when ever the user interacts with a command line input
- * @param input A structure that details the state of the input field.
- * It should look something like: { typed:a, cursor: { start:b, end:c } }
- * Where a is the contents of the input field, and b and c are the start
- * and end of the cursor/selection respectively.
- * <p>The general sequence is:
- * <ul>
- * <li>_tokenize(): convert _typed into _parts
- * <li>_split(): convert _parts into _command and _unparsedArgs
- * <li>_assign(): convert _unparsedArgs into requisition
- * </ul>
- */
- Requisition.prototype.update = function(input) {
- this.input = input;
- if (this.input.cursor == null) {
- this.input.cursor = { start: input.length, end: input.length };
- }
-
- this._structuralChangeInProgress = true;
-
- this._args = this._tokenize(input.typed);
-
- var args = this._args.slice(0); // i.e. clone
- this._split(args);
- this._assign(args);
-
- this._structuralChangeInProgress = false;
-
- this.inputChange();
- };
-
- /**
- * Empty the current buffer, and notify listeners that we're now empty
- */
- Requisition.prototype.clear = function() {
- this.update({ typed: '', cursor: { start: 0, end: 0 } });
- };
-
- /**
- * Requisition._tokenize() is a state machine. These are the states.
- */
- var In = {
- /**
- * The last character was ' '.
- * Typing a ' ' character will not change the mode
- * Typing one of '"{ will change mode to SINGLE_Q, DOUBLE_Q or SCRIPT.
- * Anything else goes into SIMPLE mode.
- */
- WHITESPACE: 1,
-
- /**
- * The last character was part of a parameter.
- * Typing ' ' returns to WHITESPACE mode. Any other character
- * (including '"{} which are otherwise special) does not change the mode.
- */
- SIMPLE: 2,
-
- /**
- * We're inside single quotes: '
- * Typing ' returns to WHITESPACE mode. Other characters do not change mode.
- */
- SINGLE_Q: 3,
-
- /**
- * We're inside double quotes: "
- * Typing " returns to WHITESPACE mode. Other characters do not change mode.
- */
- DOUBLE_Q: 4,
-
- /**
- * We're inside { and }
- * Typing } returns to WHITESPACE mode. Other characters do not change mode.
- * SCRIPT mode is slightly different from other modes in that spaces between
- * the {/} delimiters and the actual input are not considered significant.
- * e.g: " x " is a 3 character string, delimited by double quotes, however
- * { x } is a 1 character JavaScript surrounded by whitespace and {}
- * delimiters.
- * In the short term we assume that the JS routines can make sense of the
- * extra whitespace, however at some stage we may need to move the space into
- * the Argument prefix/suffix.
- * Also we don't attempt to handle nested {}. See bug 678961
- */
- SCRIPT: 5
- };
-
- /**
- * Split up the input taking into account ', " and {.
- * We don't consider \t or other classical whitespace characters to split
- * arguments apart. For one thing these characters are hard to type, but also
- * if the user has gone to the trouble of pasting a TAB character into the
- * input field (or whatever it takes), they probably mean it.
- */
- Requisition.prototype._tokenize = function(typed) {
- // For blank input, place a dummy empty argument into the list
- if (typed == null || typed.length === 0) {
- return [ new Argument('', '', '') ];
- }
-
- if (isSimple(typed)) {
- return [ new Argument(typed, '', '') ];
- }
-
- var mode = In.WHITESPACE;
-
- // First we un-escape. This list was taken from:
- // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode
- // We are generally converting to their real values except for the strings
- // '\'', '\"', '\ ', '{' and '}' which we are converting to unicode private
- // characters so we can distinguish them from '"', ' ', '{', '}' and ''',
- // which are special. They need swapping back post-split - see unescape2()
- typed = typed
- .replace(/\\\\/g, '\\')
- .replace(/\\b/g, '\b')
- .replace(/\\f/g, '\f')
- .replace(/\\n/g, '\n')
- .replace(/\\r/g, '\r')
- .replace(/\\t/g, '\t')
- .replace(/\\v/g, '\v')
- .replace(/\\n/g, '\n')
- .replace(/\\r/g, '\r')
- .replace(/\\ /g, '\uF000')
- .replace(/\\'/g, '\uF001')
- .replace(/\\"/g, '\uF002')
- .replace(/\\{/g, '\uF003')
- .replace(/\\}/g, '\uF004');
-
- function unescape2(escaped) {
- return escaped
- .replace(/\uF000/g, ' ')
- .replace(/\uF001/g, '\'')
- .replace(/\uF002/g, '"')
- .replace(/\uF003/g, '{')
- .replace(/\uF004/g, '}');
- }
-
- var i = 0; // The index of the current character
- var start = 0; // Where did this section start?
- var prefix = ''; // Stuff that comes before the current argument
- var args = []; // The array that we're creating
- var blockDepth = 0; // For JS with nested {}
-
- // This is just a state machine. We're going through the string char by char
- // The 'mode' is one of the 'In' states. As we go, we're adding Arguments
- // to the 'args' array.
-
- while (true) {
- var c = typed[i];
- switch (mode) {
- case In.WHITESPACE:
- if (c === '\'') {
- prefix = typed.substring(start, i + 1);
- mode = In.SINGLE_Q;
- start = i + 1;
- }
- else if (c === '"') {
- prefix = typed.substring(start, i + 1);
- mode = In.DOUBLE_Q;
- start = i + 1;
- }
- else if (c === '{') {
- prefix = typed.substring(start, i + 1);
- mode = In.SCRIPT;
- blockDepth++;
- start = i + 1;
- }
- else if (/ /.test(c)) {
- // Still whitespace, do nothing
- }
- else {
- prefix = typed.substring(start, i);
- mode = In.SIMPLE;
- start = i;
- }
- break;
-
- case In.SIMPLE:
- // There is an edge case of xx'xx which we are assuming to
- // be a single parameter (and same with ")
- if (c === ' ') {
- var str = unescape2(typed.substring(start, i));
- args.push(new Argument(str, prefix, ''));
- mode = In.WHITESPACE;
- start = i;
- prefix = '';
- }
- break;
-
- case In.SINGLE_Q:
- if (c === '\'') {
- var str = unescape2(typed.substring(start, i));
- args.push(new Argument(str, prefix, c));
- mode = In.WHITESPACE;
- start = i + 1;
- prefix = '';
- }
- break;
-
- case In.DOUBLE_Q:
- if (c === '"') {
- var str = unescape2(typed.substring(start, i));
- args.push(new Argument(str, prefix, c));
- mode = In.WHITESPACE;
- start = i + 1;
- prefix = '';
- }
- break;
-
- case In.SCRIPT:
- if (c === '{') {
- blockDepth++;
- }
- else if (c === '}') {
- blockDepth--;
- if (blockDepth === 0) {
- var str = unescape2(typed.substring(start, i));
- args.push(new ScriptArgument(str, prefix, c));
- mode = In.WHITESPACE;
- start = i + 1;
- prefix = '';
- }
- }
- break;
- }
-
- i++;
-
- if (i >= typed.length) {
- // There is nothing else to read - tidy up
- if (mode === In.WHITESPACE) {
- if (i !== start) {
- // There's whitespace at the end of the typed string. Add it to the
- // last argument's suffix, creating an empty argument if needed.
- var extra = typed.substring(start, i);
- var lastArg = args[args.length - 1];
- if (!lastArg) {
- args.push(new Argument('', extra, ''));
- }
- else {
- lastArg.suffix += extra;
- }
- }
- }
- else if (mode === In.SCRIPT) {
- var str = unescape2(typed.substring(start, i + 1));
- args.push(new ScriptArgument(str, prefix, ''));
- }
- else {
- var str = unescape2(typed.substring(start, i + 1));
- args.push(new Argument(str, prefix, ''));
- }
- break;
- }
- }
-
- return args;
- };
-
- /**
- * If the input has no spaces, quotes, braces or escapes,
- * we can take the fast track.
- */
- function isSimple(typed) {
- for (var i = 0; i < typed.length; i++) {
- var c = typed.charAt(i);
- if (c === ' ' || c === '"' || c === '\'' ||
- c === '{' || c === '}' || c === '\\') {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Looks in the canon for a command extension that matches what has been
- * typed at the command line.
- */
- Requisition.prototype._split = function(args) {
- // Handle the special case of the user typing { javascript(); }
- // We use the hidden 'eval' command directly rather than shift()ing one of
- // the parameters, and parse()ing it.
- if (args[0] instanceof ScriptArgument) {
- // Special case: if the user enters { console.log('foo'); } then we need to
- // use the hidden 'eval' command
- var conversion = new Conversion(evalCommand, new Argument());
- this.commandAssignment.setConversion(conversion);
- return;
- }
-
- var argsUsed = 1;
- var conversion;
-
- while (argsUsed <= args.length) {
- var arg = (argsUsed === 1) ?
- args[0] :
- new MergedArgument(args, 0, argsUsed);
- conversion = this.commandAssignment.param.type.parse(arg);
-
- // We only want to carry on if this command is a parent command,
- // which means that there is a commandAssignment, but not one with
- // an exec function.
- if (!conversion.value || conversion.value.exec) {
- break;
- }
-
- // Previously we needed a way to hide commands depending context.
- // We have not resurrected that feature yet, but if we do we should
- // insert code here to ignore certain commands depending on the
- // context/environment
-
- argsUsed++;
- }
-
- this.commandAssignment.setConversion(conversion);
-
- for (var i = 0; i < argsUsed; i++) {
- args.shift();
- }
-
- // This could probably be re-written to consume args as we go
- };
-
- /**
- * Work out which arguments are applicable to which parameters.
- */
- Requisition.prototype._assign = function(args) {
- if (!this.commandAssignment.getValue()) {
- this._unassigned.setUnassigned(args);
- return;
- }
-
- if (args.length === 0) {
- this.setDefaultArguments();
- this._unassigned.setDefault();
- return;
- }
-
- // Create an error if the command does not take parameters, but we have
- // been given them ...
- if (this.assignmentCount === 0) {
- this._unassigned.setUnassigned(args);
- return;
- }
-
- // Special case: if there is only 1 parameter, and that's of type
- // text, then we put all the params into the first param
- if (this.assignmentCount === 1) {
- var assignment = this.getAssignment(0);
- if (assignment.param.type instanceof StringType) {
- var arg = (args.length === 1) ?
- args[0] :
- new MergedArgument(args);
- var conversion = assignment.param.type.parse(arg);
- assignment.setConversion(conversion);
- this._unassigned.setDefault();
- return;
- }
- }
-
- // Positional arguments can still be specified by name, but if they are
- // then we need to ignore them when working them out positionally
- var names = this.getParameterNames();
-
- // We collect the arguments used in arrays here before assigning
- var arrayArgs = {};
-
- // Extract all the named parameters
- this.getAssignments(false).forEach(function(assignment) {
- // Loop over the arguments
- // Using while rather than loop because we remove args as we go
- var i = 0;
- while (i < args.length) {
- if (assignment.param.isKnownAs(args[i].text)) {
- var arg = args.splice(i, 1)[0];
- names = names.filter(function(test) {
- return test !== assignment.param.name;
- });
-
- // boolean parameters don't have values, default to false
- if (assignment.param.type instanceof BooleanType) {
- arg = new TrueNamedArgument(null, arg);
- }
- else {
- var valueArg = null;
- if (i + 1 >= args.length) {
- valueArg = args.splice(i, 1)[0];
- }
- arg = new NamedArgument(arg, valueArg);
- }
-
- if (assignment.param.type instanceof ArrayType) {
- var arrayArg = arrayArgs[assignment.param.name];
- if (!arrayArg) {
- arrayArg = new ArrayArgument();
- arrayArgs[assignment.param.name] = arrayArg;
- }
- arrayArg.addArgument(arg);
- }
- else {
- var conversion = assignment.param.type.parse(arg);
- assignment.setConversion(conversion);
- }
- }
- else {
- // Skip this parameter and handle as a positional parameter
- i++;
- }
- }
- }, this);
-
- // What's left are positional parameters assign in order
- names.forEach(function(name) {
- var assignment = this.getAssignment(name);
-
- // If not set positionally, and we can't set it non-positionally,
- // we have to default it to prevent previous values surviving
- if (!assignment.param.isPositionalAllowed()) {
- assignment.setDefault();
- return;
- }
-
- // If this is a positional array argument, then it swallows the
- // rest of the arguments.
- if (assignment.param.type instanceof ArrayType) {
- var arrayArg = arrayArgs[assignment.param.name];
- if (!arrayArg) {
- arrayArg = new ArrayArgument();
- arrayArgs[assignment.param.name] = arrayArg;
- }
- arrayArg.addArguments(args);
- args = [];
- }
- else {
- var arg = (args.length > 0) ?
- args.splice(0, 1)[0] :
- new Argument();
-
- var conversion = assignment.param.type.parse(arg);
- assignment.setConversion(conversion);
- }
- }, this);
-
- // Now we need to assign the array argument (if any)
- Object.keys(arrayArgs).forEach(function(name) {
- var assignment = this.getAssignment(name);
- var conversion = assignment.param.type.parse(arrayArgs[name]);
- assignment.setConversion(conversion);
- }, this);
-
- if (args.length > 0) {
- this._unassigned.setUnassigned(args);
- }
- else {
- this._unassigned.setDefault();
- }
- };
-
- exports.Requisition = Requisition;
-
-
- /**
- * Functions and data related to the execution of a command
- */
- function ExecutionContext(environment, document) {
- this.environment = environment;
- this.document = document;
- }
-
- ExecutionContext.prototype.createPromise = function() {
- return new Promise();
- };
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/promise', ['require', 'exports', 'module' ], function(require, exports, module) {
-
- Components.utils.import("resource:///modules/devtools/Promise.jsm");
- exports.Promise = Promise;
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/inputter', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/history', 'text!gcli/ui/inputter.css'], function(require, exports, module) {
- var cliView = exports;
-
-
- var KeyEvent = require('gcli/util').event.KeyEvent;
- var dom = require('gcli/util').dom;
-
- var Status = require('gcli/types').Status;
- var History = require('gcli/history').History;
-
- var inputterCss = require('text!gcli/ui/inputter.css');
-
-
- /**
- * A wrapper to take care of the functions concerning an input element
- */
- function Inputter(options) {
- this.requisition = options.requisition;
-
- // Suss out where the input element is
- this.element = options.inputElement || 'gcliInput';
- if (typeof this.element === 'string') {
- this.document = options.document || document;
- var name = this.element;
- this.element = this.document.getElementById(name);
- if (!this.element) {
- throw new Error('No element with id=' + name + '.');
- }
- }
- else {
- // Assume we've been passed in the correct node
- this.document = this.element.ownerDocument;
- }
-
- if (inputterCss != null) {
- this.style = dom.importCss(inputterCss, this.document);
- }
-
- this.element.spellcheck = false;
-
- // Used to distinguish focus from TAB in CLI. See onKeyUp()
- this.lastTabDownAt = 0;
-
- // Used to effect caret changes. See _processCaretChange()
- this._caretChange = null;
-
- // Ensure that TAB/UP/DOWN isn't handled by the browser
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.element.addEventListener('keydown', this.onKeyDown, false);
- this.element.addEventListener('keyup', this.onKeyUp, false);
-
- if (options.completer == null) {
- options.completer = new Completer(options);
- }
- else if (typeof options.completer === 'function') {
- options.completer = new options.completer(options);
- }
- this.completer = options.completer;
- this.completer.decorate(this);
-
- // Use the provided history object, or instantiate our own
- this.history = options.history = options.history || new History(options);
- this._scrollingThroughHistory = false;
-
- // Cursor position affects hint severity
- this.onMouseUp = function(ev) {
- this.completer.update(this.getInputState());
- }.bind(this);
- this.element.addEventListener('mouseup', this.onMouseUp, false);
-
- this.focusManager = options.focusManager;
- if (this.focusManager) {
- this.focusManager.addMonitoredElement(this.element, 'input');
- }
-
- this.requisition.inputChange.add(this.onInputChange, this);
- }
-
- /**
- * Avoid memory leaks
- */
- Inputter.prototype.destroy = function() {
- this.requisition.inputChange.remove(this.onInputChange, this);
- if (this.focusManager) {
- this.focusManager.removeMonitoredElement(this.element, 'input');
- }
-
- this.element.removeEventListener('keydown', this.onKeyDown, false);
- this.element.removeEventListener('keyup', this.onKeyUp, false);
- delete this.onKeyDown;
- delete this.onKeyUp;
-
- this.history.destroy();
- this.completer.destroy();
-
- if (this.style) {
- this.style.parentNode.removeChild(this.style);
- delete this.style;
- }
-
- delete this.document;
- delete this.element;
- };
-
- /**
- * Utility to add an element into the DOM after the input element
- */
- Inputter.prototype.appendAfter = function(element) {
- this.element.parentNode.insertBefore(element, this.element.nextSibling);
- };
-
- /**
- * Handler for the Requisition.inputChange event
- */
- Inputter.prototype.onInputChange = function() {
- if (this._caretChange == null) {
- // We weren't expecting a change so this was requested by the hint system
- // we should move the cursor to the end of the 'changed section', and the
- // best we can do for that right now is the end of the current argument.
- this._caretChange = Caret.TO_ARG_END;
- }
- this._setInputInternal(this.requisition.toString());
- };
-
- /**
- * Internal function to set the input field to a value.
- * This function checks to see if the current value is the same as the new
- * value, and makes no changes if they are the same (except for caret/completer
- * updating - see below). If changes are to be made, they are done in a timeout
- * to avoid XUL bug 676520.
- * This function assumes that the data model is up to date with the new value.
- * It does attempts to leave the caret position in the same position in the
- * input string unless this._caretChange === Caret.TO_ARG_END. This is required
- * for completion events.
- * It does not change the completer decoration unless this._updatePending is
- * set. This is required for completion events.
- */
- Inputter.prototype._setInputInternal = function(str, update) {
- if (!this.document) {
- return; // This can happen post-destroy()
- }
-
- if (this.element.value && this.element.value === str) {
- this._processCaretChange(this.getInputState(), false);
- return;
- }
-
- // Updating in a timeout fixes a XUL issue (bug 676520) where textbox gives
- // incorrect values for its content
- this.document.defaultView.setTimeout(function() {
- if (!this.document) {
- return; // This can happen post-destroy()
- }
-
- // Bug 678520 - We could do better caret handling by recording the caret
- // position in terms of offset into an assignment, and then replacing into
- // a similar place
- var input = this.getInputState();
- input.typed = str;
- this._processCaretChange(input);
- this.element.value = str;
-
- if (update) {
- this.update();
- }
- }.bind(this), 0);
- };
-
- /**
- * Various ways in which we need to manipulate the caret/selection position.
- * A value of null means we're not expecting a change
- */
- var Caret = {
- /**
- * We are expecting changes, but we don't need to move the cursor
- */
- NO_CHANGE: 0,
-
- /**
- * We want the entire input area to be selected
- */
- SELECT_ALL: 1,
-
- /**
- * The whole input has changed - push the cursor to the end
- */
- TO_END: 2,
-
- /**
- * A part of the input has changed - push the cursor to the end of the
- * changed section
- */
- TO_ARG_END: 3
- };
-
- /**
- * If this._caretChange === Caret.TO_ARG_END, we alter the input object to move
- * the selection start to the end of the current argument.
- * @param input An object shaped like { typed:'', cursor: { start:0, end:0 }}
- * @param forceUpdate Do we call this.completer.update even when the cursor has
- * not changed (useful when input.typed has changed)
- */
- Inputter.prototype._processCaretChange = function(input, forceUpdate) {
- var start, end;
- switch (this._caretChange) {
- case Caret.SELECT_ALL:
- start = 0;
- end = input.typed.length;
- break;
-
- case Caret.TO_END:
- start = input.typed.length;
- end = input.typed.length;
- break;
-
- case Caret.TO_ARG_END:
- // There could be a fancy way to do this involving assignment/arg math
- // but it doesn't seem easy, so we cheat a move the cursor to just before
- // the next space, or the end of the input
- start = input.cursor.start;
- do {
- start++;
- }
- while (start < input.typed.length && input.typed[start - 1] !== ' ');
-
- end = start;
- break;
-
- case null:
- case Caret.NO_CHANGE:
- start = input.cursor.start;
- end = input.cursor.end;
- break;
- }
-
- start = (start > input.typed.length) ? input.typed.length : start;
- end = (end > input.typed.length) ? input.typed.length : end;
-
- var newInput = { typed: input.typed, cursor: { start: start, end: end }};
- if (start !== input.cursor.start || end !== input.cursor.end || forceUpdate) {
- this.completer.update(newInput);
- }
-
- this.element.selectionStart = newInput.cursor.start;
- this.element.selectionEnd = newInput.cursor.end;
-
- this._caretChange = null;
- return newInput;
- };
-
- /**
- * Set the input field to a value.
- * This function updates the data model and the completer decoration. It sets
- * the caret to the end of the input. It does not make any similarity checks
- * so calling this function with it's current value resets the cursor position.
- * It does not execute the input or affect the history.
- * This function should not be called internally, by Inputter and never as a
- * result of a keyboard event on this.element or bug 676520 could be triggered.
- */
- Inputter.prototype.setInput = function(str) {
- this.element.value = str;
- this.update();
- };
-
- /**
- * Focus the input element
- */
- Inputter.prototype.focus = function() {
- this.element.focus();
- };
-
- /**
- * Ensure certain keys (arrows, tab, etc) that we would like to handle
- * are not handled by the browser
- */
- Inputter.prototype.onKeyDown = function(ev) {
- if (ev.keyCode === KeyEvent.DOM_VK_UP || ev.keyCode === KeyEvent.DOM_VK_DOWN) {
- ev.preventDefault();
- }
- if (ev.keyCode === KeyEvent.DOM_VK_TAB) {
- this.lastTabDownAt = 0;
- if (!ev.shiftKey) {
- ev.preventDefault();
- // Record the timestamp of this TAB down so onKeyUp can distinguish
- // focus from TAB in the CLI.
- this.lastTabDownAt = ev.timeStamp;
- }
- if (ev.metaKey || ev.altKey || ev.crtlKey) {
- if (this.document.commandDispatcher) {
- this.document.commandDispatcher.advanceFocus();
- }
- else {
- this.element.blur();
- }
- }
- }
- };
-
- /**
- * The main keyboard processing loop
- */
- Inputter.prototype.onKeyUp = function(ev) {
- // RETURN does a special exec/highlight thing
- if (ev.keyCode === KeyEvent.DOM_VK_RETURN) {
- var worst = this.requisition.getStatus();
- // Deny RETURN unless the command might work
- if (worst === Status.VALID) {
- this._scrollingThroughHistory = false;
- this.history.add(this.element.value);
- this.requisition.exec();
- }
- // See bug 664135 - On pressing return with an invalid input, GCLI
- // should select the incorrect part of the input for an easy fix
- return;
- }
-
- if (ev.keyCode === KeyEvent.DOM_VK_TAB && !ev.shiftKey) {
- // If the TAB keypress took the cursor from another field to this one,
- // then they get the keydown/keypress, and we get the keyup. In this
- // case we don't want to do any completion.
- // If the time of the keydown/keypress of TAB was close (i.e. within
- // 1 second) to the time of the keyup then we assume that we got them
- // both, and do the completion.
- if (this.lastTabDownAt + 1000 > ev.timeStamp) {
- this.getCurrentAssignment().complete();
- // It's possible for TAB to not change the input, in which case the
- // onInputChange event will not fire, and the caret move will not be
- // processed. So we check that this is done
- this._caretChange = Caret.TO_ARG_END;
- this._processCaretChange(this.getInputState(), true);
- }
- this.lastTabDownAt = 0;
- this._scrollingThroughHistory = false;
- return;
- }
-
- if (ev.keyCode === KeyEvent.DOM_VK_UP) {
- if (this.element.value === '' || this._scrollingThroughHistory) {
- this._scrollingThroughHistory = true;
- this._setInputInternal(this.history.backward(), true);
- }
- else {
- this.getCurrentAssignment().increment();
- }
- return;
- }
-
- if (ev.keyCode === KeyEvent.DOM_VK_DOWN) {
- if (this.element.value === '' || this._scrollingThroughHistory) {
- this._scrollingThroughHistory = true;
- this._setInputInternal(this.history.forward(), true);
- }
- else {
- this.getCurrentAssignment().decrement();
- }
- return;
- }
-
- this._scrollingThroughHistory = false;
- this._caretChange = Caret.NO_CHANGE;
- this.update();
- };
-
- /**
- * Accessor for the assignment at the cursor.
- * i.e Requisition.getAssignmentAt(cursorPos);
- */
- Inputter.prototype.getCurrentAssignment = function() {
- var start = this.element.selectionStart;
- return this.requisition.getAssignmentAt(start);
- };
-
- /**
- * Actually parse the input and make sure we're all up to date
- */
- Inputter.prototype.update = function() {
- var input = this.getInputState();
- this.requisition.update(input);
- this.completer.update(input);
- };
-
- /**
- * Pull together an input object, which may include XUL hacks
- */
- Inputter.prototype.getInputState = function() {
- var input = {
- typed: this.element.value,
- cursor: {
- start: this.element.selectionStart,
- end: this.element.selectionEnd
- }
- };
-
- // Workaround for potential XUL bug 676520 where textbox gives incorrect
- // values for its content
- if (input.typed == null) {
- input = { typed: '', cursor: { start: 0, end: 0 } };
- console.log('fixing input.typed=""', input);
- }
-
- return input;
- };
-
- cliView.Inputter = Inputter;
-
-
- /**
- * Completer is an 'input-like' element that sits an input element annotating
- * it with visual goodness.
- * @param {object} options An object that contains various options which
- * customizes how the completer functions.
- * Properties on the options object:
- * - document (required) DOM document to be used in creating elements
- * - requisition (required) A GCLI Requisition object whose state is monitored
- * - completeElement (optional) An element to use
- * - completionPrompt (optional) The prompt to show before a completion.
- * Defaults to '»' (double greater-than, a.k.a right guillemet).
- */
- function Completer(options) {
- this.document = options.document;
- this.requisition = options.requisition;
- this.elementCreated = false;
-
- this.element = options.completeElement || 'gcliComplete';
- if (typeof this.element === 'string') {
- var name = this.element;
- this.element = this.document.getElementById(name);
-
- if (!this.element) {
- this.elementCreated = true;
- this.element = dom.createElement(this.document, 'div');
- this.element.className = 'gcliCompletion gcliVALID';
- this.element.setAttribute('tabindex', '-1');
- this.element.setAttribute('aria-live', 'polite');
- }
- }
-
- this.completionPrompt = typeof options.completionPrompt === 'string'
- ? options.completionPrompt
- : '»';
-
- if (options.inputBackgroundElement) {
- this.backgroundElement = options.inputBackgroundElement;
- }
- else {
- this.backgroundElement = this.element;
- }
- }
-
- /**
- * Avoid memory leaks
- */
- Completer.prototype.destroy = function() {
- delete this.document;
- delete this.element;
- delete this.backgroundElement;
-
- if (this.elementCreated) {
- this.document.defaultView.removeEventListener('resize', this.resizer, false);
- }
-
- delete this.inputter;
- };
-
- /**
- * A list of the styles that decorate() should copy to make the completion
- * element look like the input element. backgroundColor is a spiritual part of
- * this list, but see comment in decorate().
- */
- Completer.copyStyles = [ 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle' ];
-
- /**
- * Make ourselves visually similar to the input element, and make the input
- * element transparent so our background shines through
- */
- Completer.prototype.decorate = function(inputter) {
- this.inputter = inputter;
- var input = inputter.element;
-
- // If we were told which element to use, then assume it is already
- // correctly positioned. Otherwise insert it alongside the input element
- if (this.elementCreated) {
- this.inputter.appendAfter(this.element);
-
- var styles = this.document.defaultView.getComputedStyle(input, null);
- Completer.copyStyles.forEach(function(style) {
- this.element.style[style] = styles[style];
- }, this);
-
- // The completer text is by default invisible so we make it the same color
- // as the input background.
- this.element.style.color = input.style.backgroundColor;
-
- // If there is a separate backgroundElement, then we make the element
- // transparent, otherwise it inherits the color of the input node
- // It's not clear why backgroundColor doesn't work when used from
- // computedStyle, but it doesn't. Patches welcome!
- this.element.style.backgroundColor = (this.backgroundElement != this.element) ?
- 'transparent' :
- input.style.backgroundColor;
- input.style.backgroundColor = 'transparent';
-
- // Make room for the prompt
- input.style.paddingLeft = '20px';
-
- this.resizer = this.resizer.bind(this);
- this.document.defaultView.addEventListener('resize', this.resizer, false);
- this.resizer();
- }
- };
-
- /**
- * Ensure that the completion element is the same size and the inputter element
- */
- Completer.prototype.resizer = function() {
- var rect = this.inputter.element.getBoundingClientRect();
- // -4 to line up with 1px of padding and border, top and bottom
- var height = rect.bottom - rect.top - 4;
-
- this.element.style.top = rect.top + 'px';
- this.element.style.height = height + 'px';
- this.element.style.lineHeight = height + 'px';
- this.element.style.left = rect.left + 'px';
- this.element.style.width = (rect.right - rect.left) + 'px';
- };
-
- /**
- * Is the completion given, a "strict" completion of the user inputted value?
- * A completion is considered "strict" only if it the user inputted value is an
- * exact prefix of the completion (ignoring leading whitespace)
- */
- function isStrictCompletion(inputValue, completion) {
- // Strip any leading whitespace from the user inputted value because the
- // completion will never have leading whitespace.
- inputValue = inputValue.replace(/^\s*/, '');
- // Strict: "ec" -> "echo"
- // Non-Strict: "ls *" -> "ls foo bar baz"
- return completion.indexOf(inputValue) === 0;
- }
-
- /**
- * Bring the completion element up to date with what the requisition says
- */
- Completer.prototype.update = function(input) {
- var current = this.requisition.getAssignmentAt(input.cursor.start);
- var predictions = current.getPredictions();
-
- var completion = '<span class="gcliPrompt">' + this.completionPrompt + '</span> ';
- if (input.typed.length > 0) {
- var scores = this.requisition.getInputStatusMarkup();
- completion += this.markupStatusScore(scores, input);
- }
-
- if (input.typed.length > 0 && predictions.length > 0) {
- var tab = predictions[0].name;
- var existing = current.getArg().text;
- if (isStrictCompletion(existing, tab) && input.cursor.start === input.typed.length) {
- // Display the suffix of the prediction as the completion.
- var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
- var suffix = tab.slice(existing.length - numLeadingSpaces);
- completion += '<span class="gcliCompl">' + suffix + '</span>';
- } else {
- // Display the '-> prediction' at the end of the completer element
- completion += ' <span class="gcliCompl">⇥ ' +
- tab + '</span>';
- }
- }
-
- // A hack to add a grey '}' to the end of the command line when we've opened
- // with a { but haven't closed it
- var command = this.requisition.commandAssignment.getValue();
- if (command && command.name === '{') {
- if (this.requisition.getAssignment(0).getArg().suffix.indexOf('}') === -1) {
- completion += '<span class="gcliCloseBrace">}</span>';
- }
- }
-
- dom.setInnerHtml(this.element, '<span>' + completion + '</span>');
- };
-
- /**
- * Mark-up an array of Status values with spans
- */
- Completer.prototype.markupStatusScore = function(scores, input) {
- var completion = '';
- if (scores.length === 0) {
- return completion;
- }
-
- var i = 0;
- var lastStatus = -1;
- while (true) {
- if (lastStatus !== scores[i]) {
- var state = scores[i];
- if (!state) {
- console.error('No state at i=' + i + '. scores.len=' + scores.length);
- state = Status.VALID;
- }
- completion += '<span class="gcli' + state.toString() + '">';
- lastStatus = scores[i];
- }
- var char = input.typed[i];
- if (char === ' ') {
- char = ' ';
- }
- completion += char;
- i++;
- if (i === input.typed.length) {
- completion += '</span>';
- break;
- }
- if (lastStatus !== scores[i]) {
- completion += '</span>';
- }
- }
-
- return completion;
- };
-
- cliView.Completer = Completer;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/history', ['require', 'exports', 'module' ], function(require, exports, module) {
-
- /**
- * A History object remembers commands that have been entered in the past and
- * provides an API for accessing them again.
- * See Bug 681340: Search through history (like C-r in bash)?
- */
- function History() {
- // This is the actual buffer where previous commands are kept.
- // 'this._buffer[0]' should always be equal the empty string. This is so
- // that when you try to go in to the "future", you will just get an empty
- // command.
- this._buffer = [''];
-
- // This is an index in to the history buffer which points to where we
- // currently are in the history.
- this._current = 0;
- }
-
- /**
- * Avoid memory leaks
- */
- History.prototype.destroy = function() {
- // delete this._buffer;
- };
-
- /**
- * Record and save a new command in the history.
- */
- History.prototype.add = function(command) {
- this._buffer.splice(1, 0, command);
- this._current = 0;
- };
-
- /**
- * Get the next (newer) command from history.
- */
- History.prototype.forward = function() {
- if (this._current > 0 ) {
- this._current--;
- }
- return this._buffer[this._current];
- };
-
- /**
- * Get the previous (older) item from history.
- */
- History.prototype.backward = function() {
- if (this._current < this._buffer.length - 1) {
- this._current++;
- }
- return this._buffer[this._current];
- };
-
- exports.History = History;
-
- });define("text!gcli/ui/inputter.css", [], void 0);
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/arg_fetch', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/ui/field', 'gcli/ui/domtemplate', 'text!gcli/ui/arg_fetch.css', 'text!gcli/ui/arg_fetch.html'], function(require, exports, module) {
- var argFetch = exports;
-
-
- var dom = require('gcli/util').dom;
- var Status = require('gcli/types').Status;
-
- var getField = require('gcli/ui/field').getField;
- var Templater = require('gcli/ui/domtemplate').Templater;
-
- var editorCss = require('text!gcli/ui/arg_fetch.css');
- var argFetchHtml = require('text!gcli/ui/arg_fetch.html');
-
-
- /**
- * A widget to display an inline dialog which allows the user to fill out
- * the arguments to a command.
- * @param document The document to use in creating widgets
- * @param requisition The Requisition to fill out
- */
- function ArgFetcher(document, requisition) {
- this.document = document;
- this.requisition = requisition;
-
- // FF can be really hard to debug if doc is null, so we check early on
- if (!this.document) {
- throw new Error('No document');
- }
-
- this.element = dom.createElement(this.document, 'div');
- this.element.className = 'gcliCliEle';
- // We cache the fields we create so we can destroy them later
- this.fields = [];
-
- this.tmpl = new Templater();
- // Populated by template
- this.okElement = null;
-
- // Pull the HTML into the DOM, but don't add it to the document
- if (editorCss != null) {
- this.style = dom.importCss(editorCss, this.document);
- }
-
- var templates = dom.createElement(this.document, 'div');
- dom.setInnerHtml(templates, argFetchHtml);
- this.reqTempl = templates.querySelector('#gcliReqTempl');
-
- this.requisition.commandChange.add(this.onCommandChange, this);
- this.requisition.inputChange.add(this.onInputChange, this);
- }
-
- /**
- * Avoid memory leaks
- */
- ArgFetcher.prototype.destroy = function() {
- this.requisition.inputChange.remove(this.onInputChange, this);
- this.requisition.commandChange.remove(this.onCommandChange, this);
-
- if (this.style) {
- this.style.parentNode.removeChild(this.style);
- delete this.style;
- }
-
- this.fields.forEach(function(field) { field.destroy(); });
-
- delete this.document;
- delete this.element;
- delete this.okElement;
- delete this.reqTempl;
- };
-
- /**
- * Called whenever the command part of the requisition changes
- */
- ArgFetcher.prototype.onCommandChange = function(ev) {
- var command = this.requisition.commandAssignment.getValue();
- if (!command || !command.exec) {
- this.element.style.display = 'none';
- }
- else {
- if (ev && ev.oldValue === ev.newValue) {
- // Just the text has changed
- return;
- }
-
- this.fields.forEach(function(field) { field.destroy(); });
- this.fields = [];
-
- var reqEle = this.reqTempl.cloneNode(true);
- this.tmpl.processNode(reqEle, this);
- dom.clearElement(this.element);
- this.element.appendChild(reqEle);
-
- var status = this.requisition.getStatus();
- this.okElement.disabled = (status === Status.VALID);
-
- this.element.style.display = 'block';
- }
- };
-
- /**
- * Called whenever the text input of the requisition changes
- */
- ArgFetcher.prototype.onInputChange = function(ev) {
- var command = this.requisition.commandAssignment.getValue();
- if (command && command.exec) {
- var status = this.requisition.getStatus();
- this.okElement.disabled = (status !== Status.VALID);
- }
- };
-
- /**
- * Called by the template process in #onCommandChange() to get an instance
- * of field for each assignment.
- */
- ArgFetcher.prototype.getInputFor = function(assignment) {
- var newField = getField(assignment.param.type, {
- document: this.document,
- type: assignment.param.type,
- name: assignment.param.name,
- requisition: this.requisition,
- required: assignment.param.isDataRequired(),
- named: !assignment.param.isPositionalAllowed()
- });
-
- // BUG 664198 - remove on delete
- newField.fieldChanged.add(function(ev) {
- assignment.setConversion(ev.conversion);
- }, this);
- assignment.assignmentChange.add(function(ev) {
- newField.setConversion(ev.conversion);
- }.bind(this));
-
- this.fields.push(newField);
- newField.setConversion(this.assignment.conversion);
-
- // Bug 681894: we add the field as a property of the assignment so that
- // #linkMessageElement() can call 'field.setMessageElement(element)'
- assignment.field = newField;
-
- return newField.element;
- };
-
- /**
- * Called by the template to setup an mutable message field
- */
- ArgFetcher.prototype.linkMessageElement = function(assignment, element) {
- // Bug 681894: See comment in getInputFor()
- var field = assignment.field;
- delete assignment.field;
- if (field == null) {
- console.error('Missing field for ' + assignment.param.name);
- return 'Missing field';
- }
- field.setMessageElement(element);
- return '';
- };
-
- /**
- * Event handler added by the template menu.html
- */
- ArgFetcher.prototype.onFormOk = function(ev) {
- this.requisition.exec();
- };
-
- /**
- * Event handler added by the template menu.html
- */
- ArgFetcher.prototype.onFormCancel = function(ev) {
- this.requisition.clear();
- };
-
- argFetch.ArgFetcher = ArgFetcher;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/field', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/argument', 'gcli/types', 'gcli/types/basic', 'gcli/types/javascript', 'gcli/ui/menu'], function(require, exports, module) {
-
-
- var dom = require('gcli/util').dom;
- var createEvent = require('gcli/util').createEvent;
- var l10n = require('gcli/l10n');
-
- var Argument = require('gcli/argument').Argument;
- var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
- var FalseNamedArgument = require('gcli/argument').FalseNamedArgument;
- var ArrayArgument = require('gcli/argument').ArrayArgument;
-
- var Status = require('gcli/types').Status;
- var Conversion = require('gcli/types').Conversion;
- var ArrayConversion = require('gcli/types').ArrayConversion;
-
- var StringType = require('gcli/types/basic').StringType;
- var NumberType = require('gcli/types/basic').NumberType;
- var BooleanType = require('gcli/types/basic').BooleanType;
- var BlankType = require('gcli/types/basic').BlankType;
- var SelectionType = require('gcli/types/basic').SelectionType;
- var DeferredType = require('gcli/types/basic').DeferredType;
- var ArrayType = require('gcli/types/basic').ArrayType;
- var JavascriptType = require('gcli/types/javascript').JavascriptType;
-
- var Menu = require('gcli/ui/menu').Menu;
-
-
- /**
- * A Field is a way to get input for a single parameter.
- * This class is designed to be inherited from. It's important that all
- * subclasses have a similar constructor signature because they are created
- * via getField(...)
- * @param document The document we use in calling createElement
- * @param type The type to use in conversions
- * @param named Is this parameter named? That is to say, are positional
- * arguments disallowed, if true, then we need to provide updates to the
- * command line that explicitly name the parameter in use (e.g. --verbose, or
- * --name Fred rather than just true or Fred)
- * @param name If this parameter is named, what name should we use
- * @param requ The requisition that we're attached to
- */
- function Field(document, type, named, name, requ) {
- }
-
- /**
- * Subclasses should assign their element with the DOM node that gets added
- * to the 'form'. It doesn't have to be an input node, just something that
- * contains it.
- */
- Field.prototype.element = undefined;
-
- /**
- * Indicates that this field should drop any resources that it has created
- */
- Field.prototype.destroy = function() {
- delete this.messageElement;
- };
-
- /**
- * Update this field display with the value from this conversion.
- * Subclasses should provide an implementation of this function.
- */
- Field.prototype.setConversion = function(conversion) {
- throw new Error('Field should not be used directly');
- };
-
- /**
- * Extract a conversion from the values in this field.
- * Subclasses should provide an implementation of this function.
- */
- Field.prototype.getConversion = function() {
- throw new Error('Field should not be used directly');
- };
-
- /**
- * Set the element where messages and validation errors will be displayed
- * @see setMessage()
- */
- Field.prototype.setMessageElement = function(element) {
- this.messageElement = element;
- };
-
- /**
- * Display a validation message in the UI
- */
- Field.prototype.setMessage = function(message) {
- if (this.messageElement) {
- if (message == null) {
- message = '';
- }
- dom.setInnerHtml(this.messageElement, message);
- }
- };
-
- /**
- * Method to be called by subclasses when their input changes, which allows us
- * to properly pass on the fieldChanged event.
- */
- Field.prototype.onInputChange = function() {
- var conversion = this.getConversion();
- this.fieldChanged({ conversion: conversion });
- this.setMessage(conversion.message);
- };
-
- /**
- * 'static/abstract' method to allow implementations of Field to lay a claim
- * to a type. This allows claims of various strength to be weighted up.
- * See the Field.*MATCH values.
- */
- Field.claim = function() {
- throw new Error('Field should not be used directly');
- };
- Field.MATCH = 5;
- Field.DEFAULT_MATCH = 4;
- Field.IF_NOTHING_BETTER = 1;
- Field.NO_MATCH = 0;
-
-
- /**
- * Managing the current list of Fields
- */
- var fieldCtors = [];
- function addField(fieldCtor) {
- if (typeof fieldCtor !== 'function') {
- console.error('addField erroring on ', fieldCtor);
- throw new Error('addField requires a Field constructor');
- }
- fieldCtors.push(fieldCtor);
- }
-
- function removeField(field) {
- if (typeof field !== 'string') {
- fields = fields.filter(function(test) {
- return test !== field;
- });
- delete fields[field];
- }
- else if (field instanceof Field) {
- removeField(field.name);
- }
- else {
- console.error('removeField erroring on ', field);
- throw new Error('removeField requires an instance of Field');
- }
- }
-
- function getField(type, options) {
- var ctor;
- var highestClaim = -1;
- fieldCtors.forEach(function(fieldCtor) {
- var claim = fieldCtor.claim(type);
- if (claim > highestClaim) {
- highestClaim = claim;
- ctor = fieldCtor;
- }
- });
-
- if (!ctor) {
- console.error('Unknown field type ', type, ' in ', fieldCtors);
- throw new Error('Can\'t find field for ' + type);
- }
-
- return new ctor(type, options);
- }
-
- exports.Field = Field;
- exports.addField = addField;
- exports.removeField = removeField;
- exports.getField = getField;
-
-
- /**
- * A field that allows editing of strings
- */
- function StringField(type, options) {
- this.document = options.document;
- this.type = type;
- this.arg = new Argument();
-
- this.element = dom.createElement(this.document, 'input');
- this.element.type = 'text';
- this.element.style.width = '100%';
-
- this.onInputChange = this.onInputChange.bind(this);
- this.element.addEventListener('keyup', this.onInputChange, false);
-
- this.fieldChanged = createEvent('StringField.fieldChanged');
- }
-
- StringField.prototype = Object.create(Field.prototype);
-
- StringField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.element.removeEventListener('keyup', this.onInputChange, false);
- delete this.element;
- delete this.document;
- delete this.onInputChange;
- };
-
- StringField.prototype.setConversion = function(conversion) {
- this.arg = conversion.arg;
- this.element.value = conversion.arg.text;
- this.setMessage(conversion.message);
- };
-
- StringField.prototype.getConversion = function() {
- // This tweaks the prefix/suffix of the argument to fit
- this.arg = this.arg.beget(this.element.value, { prefixSpace: true });
- return this.type.parse(this.arg);
- };
-
- StringField.claim = function(type) {
- return type instanceof StringType ? Field.MATCH : Field.IF_NOTHING_BETTER;
- };
-
- exports.StringField = StringField;
- addField(StringField);
-
-
- /**
- * A field that allows editing of numbers using an [input type=number] field
- */
- function NumberField(type, options) {
- this.document = options.document;
- this.type = type;
- this.arg = new Argument();
-
- this.element = dom.createElement(this.document, 'input');
- this.element.type = 'number';
- if (this.type.max) {
- this.element.max = this.type.max;
- }
- if (this.type.min) {
- this.element.min = this.type.min;
- }
- if (this.type.step) {
- this.element.step = this.type.step;
- }
-
- this.onInputChange = this.onInputChange.bind(this);
- this.element.addEventListener('keyup', this.onInputChange, false);
-
- this.fieldChanged = createEvent('NumberField.fieldChanged');
- }
-
- NumberField.prototype = Object.create(Field.prototype);
-
- NumberField.claim = function(type) {
- return type instanceof NumberType ? Field.MATCH : Field.NO_MATCH;
- };
-
- NumberField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.element.removeEventListener('keyup', this.onInputChange, false);
- delete this.element;
- delete this.document;
- delete this.onInputChange;
- };
-
- NumberField.prototype.setConversion = function(conversion) {
- this.arg = conversion.arg;
- this.element.value = conversion.arg.text;
- this.setMessage(conversion.message);
- };
-
- NumberField.prototype.getConversion = function() {
- this.arg = this.arg.beget(this.element.value, { prefixSpace: true });
- return this.type.parse(this.arg);
- };
-
- exports.NumberField = NumberField;
- addField(NumberField);
-
-
- /**
- * A field that uses a checkbox to toggle a boolean field
- */
- function BooleanField(type, options) {
- this.document = options.document;
- this.type = type;
- this.name = options.name;
- this.named = options.named;
-
- this.element = dom.createElement(this.document, 'input');
- this.element.type = 'checkbox';
- this.element.id = 'gcliForm' + this.name;
-
- this.onInputChange = this.onInputChange.bind(this);
- this.element.addEventListener('change', this.onInputChange, false);
-
- this.fieldChanged = createEvent('BooleanField.fieldChanged');
- }
-
- BooleanField.prototype = Object.create(Field.prototype);
-
- BooleanField.claim = function(type) {
- return type instanceof BooleanType ? Field.MATCH : Field.NO_MATCH;
- };
-
- BooleanField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.element.removeEventListener('change', this.onInputChange, false);
- delete this.element;
- delete this.document;
- delete this.onInputChange;
- };
-
- BooleanField.prototype.setConversion = function(conversion) {
- this.element.checked = conversion.value;
- this.setMessage(conversion.message);
- };
-
- BooleanField.prototype.getConversion = function() {
- var value = this.element.checked;
- var arg = this.named ?
- value ? new TrueNamedArgument(this.name) : new FalseNamedArgument() :
- new Argument(' ' + value);
- return new Conversion(value, arg);
- };
-
- exports.BooleanField = BooleanField;
- addField(BooleanField);
-
-
- /**
- * Model an instanceof SelectionType as a select input box.
- * <p>There are 3 slightly overlapping concepts to be aware of:
- * <ul>
- * <li>value: This is the (probably non-string) value, known as a value by the
- * assignment
- * <li>optValue: This is the text value as known by the DOM option element, as
- * in <option value=???%gt...
- * <li>optText: This is the contents of the DOM option element.
- * </ul>
- */
- function SelectionField(type, options) {
- this.document = options.document;
- this.type = type;
- this.items = [];
-
- this.element = dom.createElement(this.document, 'select');
- this.element.style.width = '180px';
- this._addOption({
- name: l10n.lookupFormat('fieldSelectionSelect', [ options.name ])
- });
- var lookup = this.type.getLookup();
- lookup.forEach(this._addOption, this);
-
- this.onInputChange = this.onInputChange.bind(this);
- this.element.addEventListener('change', this.onInputChange, false);
-
- this.fieldChanged = createEvent('SelectionField.fieldChanged');
- }
-
- SelectionField.prototype = Object.create(Field.prototype);
-
- SelectionField.claim = function(type) {
- return type instanceof SelectionType ? Field.DEFAULT_MATCH : Field.NO_MATCH;
- };
-
- SelectionField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.element.removeEventListener('change', this.onInputChange, false);
- delete this.element;
- delete this.document;
- delete this.onInputChange;
- };
-
- SelectionField.prototype.setConversion = function(conversion) {
- var index;
- this.items.forEach(function(item) {
- if (item.value && item.value === conversion.value) {
- index = item.index;
- }
- }, this);
- this.element.value = index;
- this.setMessage(conversion.message);
- };
-
- SelectionField.prototype.getConversion = function() {
- var item = this.items[this.element.value];
- var arg = new Argument(item.name, ' ');
- var value = item.value ? item.value : item;
- return new Conversion(value, arg);
- };
-
- SelectionField.prototype._addOption = function(item) {
- item.index = this.items.length;
- this.items.push(item);
-
- var option = dom.createElement(this.document, 'option');
- option.innerHTML = item.name;
- option.value = item.index;
- this.element.appendChild(option);
- };
-
- exports.SelectionField = SelectionField;
- addField(SelectionField);
-
-
- /**
- * A field that allows editing of javascript
- */
- function JavascriptField(type, options) {
- this.document = options.document;
- this.type = type;
- this.requ = options.requisition;
-
- this.onInputChange = this.onInputChange.bind(this);
- this.arg = new Argument('', '{ ', ' }');
-
- this.element = dom.createElement(this.document, 'div');
-
- this.input = dom.createElement(this.document, 'input');
- this.input.type = 'text';
- this.input.addEventListener('keyup', this.onInputChange, false);
- this.input.style.marginBottom = '0px';
- this.input.style.width = options.name.length === 0 ? '240px' : '160px';
- this.element.appendChild(this.input);
-
- this.menu = new Menu(this.document, { field: true });
- this.element.appendChild(this.menu.element);
-
- this.setConversion(this.type.parse(new Argument('')));
-
- this.fieldChanged = createEvent('JavascriptField.fieldChanged');
-
- // i.e. Register this.onItemClick as the default action for a menu click
- this.menu.onItemClick = this.onItemClick.bind(this);
- }
-
- JavascriptField.prototype = Object.create(Field.prototype);
-
- JavascriptField.claim = function(type) {
- return type instanceof JavascriptType ? Field.MATCH : Field.NO_MATCH;
- };
-
- JavascriptField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.input.removeEventListener('keyup', this.onInputChange, false);
- this.menu.destroy();
- delete this.element;
- delete this.input;
- delete this.menu;
- delete this.document;
- delete this.onInputChange;
- };
-
- JavascriptField.prototype.setConversion = function(conversion) {
- this.arg = conversion.arg;
- this.input.value = conversion.arg.text;
-
- var prefixLen = 0;
- if (this.type instanceof JavascriptType) {
- var typed = conversion.arg.text;
- var lastDot = typed.lastIndexOf('.');
- if (lastDot !== -1) {
- prefixLen = lastDot;
- }
- }
-
- var items = [];
- var predictions = conversion.getPredictions();
- predictions.forEach(function(item) {
- // Commands can be hidden
- if (!item.hidden) {
- items.push({
- name: item.name.substring(prefixLen),
- complete: item.name,
- description: item.description || ''
- });
- }
- }, this);
-
- this.menu.show(items);
- this.setMessage(conversion.message);
- };
-
- JavascriptField.prototype.onItemClick = function(ev) {
- this.item = ev.currentTarget.item;
- this.arg = this.arg.beget(this.item.complete, { normalize: true });
- var conversion = this.type.parse(this.arg);
- this.fieldChanged({ conversion: conversion });
- this.setMessage(conversion.message);
- };
-
- JavascriptField.prototype.onInputChange = function(ev) {
- this.item = ev.currentTarget.item;
- var conversion = this.getConversion();
- this.fieldChanged({ conversion: conversion });
- this.setMessage(conversion.message);
- };
-
- JavascriptField.prototype.getConversion = function() {
- // This tweaks the prefix/suffix of the argument to fit
- this.arg = this.arg.beget(this.input.value, { normalize: true });
- return this.type.parse(this.arg);
- };
-
- JavascriptField.DEFAULT_VALUE = '__JavascriptField.DEFAULT_VALUE';
-
- exports.JavascriptField = JavascriptField;
- addField(JavascriptField);
-
-
- /**
- * A field that works with deferred types by delaying resolution until that
- * last possible time
- */
- function DeferredField(type, options) {
- this.document = options.document;
- this.type = type;
- this.options = options;
- this.requisition = options.requisition;
- this.requisition.assignmentChange.add(this.update, this);
-
- this.element = dom.createElement(this.document, 'div');
- this.update();
-
- this.fieldChanged = createEvent('DeferredField.fieldChanged');
- }
-
- DeferredField.prototype = Object.create(Field.prototype);
-
- DeferredField.prototype.update = function() {
- var subtype = this.type.defer();
- if (subtype === this.subtype) {
- return;
- }
-
- if (this.field) {
- this.field.destroy();
- }
-
- this.subtype = subtype;
- this.field = getField(subtype, this.options);
- this.field.fieldChanged.add(this.fieldChanged, this);
-
- dom.clearElement(this.element);
- this.element.appendChild(this.field.element);
- };
-
- DeferredField.claim = function(type) {
- return type instanceof DeferredType ? Field.MATCH : Field.NO_MATCH;
- };
-
- DeferredField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.requisition.assignmentChange.remove(this.update, this);
- delete this.element;
- delete this.document;
- delete this.onInputChange;
- };
-
- DeferredField.prototype.setConversion = function(conversion) {
- this.field.setConversion(conversion);
- };
-
- DeferredField.prototype.getConversion = function() {
- return this.field.getConversion();
- };
-
- exports.DeferredField = DeferredField;
- addField(DeferredField);
-
-
- /**
- * For use with deferred types that do not yet have anything to resolve to.
- * BlankFields are not for general use.
- */
- function BlankField(type, options) {
- this.document = options.document;
- this.type = type;
- this.element = dom.createElement(this.document, 'div');
-
- this.fieldChanged = createEvent('BlankField.fieldChanged');
- }
-
- BlankField.prototype = Object.create(Field.prototype);
-
- BlankField.claim = function(type) {
- return type instanceof BlankType ? Field.MATCH : Field.NO_MATCH;
- };
-
- BlankField.prototype.setConversion = function() { };
-
- BlankField.prototype.getConversion = function() {
- return new Conversion(null);
- };
-
- exports.BlankField = BlankField;
- addField(BlankField);
-
-
- /**
- * Adds add/delete buttons to a normal field allowing there to be many values
- * given for a parameter.
- */
- function ArrayField(type, options) {
- this.document = options.document;
- this.type = type;
- this.options = options;
- this.requ = options.requisition;
-
- this._onAdd = this._onAdd.bind(this);
- this.members = [];
-
- // <div class=gcliArrayParent save="${element}">
- this.element = dom.createElement(this.document, 'div');
- this.element.className = 'gcliArrayParent';
-
- // <button class=gcliArrayMbrAdd onclick="${_onAdd}" save="${addButton}">Add
- this.addButton = dom.createElement(this.document, 'button');
- this.addButton.className = 'gcliArrayMbrAdd';
- this.addButton.addEventListener('click', this._onAdd, false);
- this.addButton.innerHTML = l10n.lookup('fieldArrayAdd');
- this.element.appendChild(this.addButton);
-
- // <div class=gcliArrayMbrs save="${mbrElement}">
- this.container = dom.createElement(this.document, 'div');
- this.container.className = 'gcliArrayMbrs';
- this.element.appendChild(this.container);
-
- this.onInputChange = this.onInputChange.bind(this);
-
- this.fieldChanged = createEvent('ArrayField.fieldChanged');
- }
-
- ArrayField.prototype = Object.create(Field.prototype);
-
- ArrayField.claim = function(type) {
- return type instanceof ArrayType ? Field.MATCH : Field.NO_MATCH;
- };
-
- ArrayField.prototype.destroy = function() {
- Field.prototype.destroy.call(this);
- this.addButton.removeEventListener('click', this._onAdd, false);
- };
-
- ArrayField.prototype.setConversion = function(conversion) {
- // BUG 653568: this is too brutal - it removes focus from any the current field
- dom.clearElement(this.container);
- this.members = [];
-
- conversion.conversions.forEach(function(subConversion) {
- this._onAdd(null, subConversion);
- }, this);
- };
-
- ArrayField.prototype.getConversion = function() {
- var conversions = [];
- var arrayArg = new ArrayArgument();
- for (var i = 0; i < this.members.length; i++) {
- var conversion = this.members[i].field.getConversion();
- conversions.push(conversion);
- arrayArg.addArgument(conversion.arg);
- }
- return new ArrayConversion(conversions, arrayArg);
- };
-
- ArrayField.prototype._onAdd = function(ev, subConversion) {
- // <div class=gcliArrayMbr save="${element}">
- var element = dom.createElement(this.document, 'div');
- element.className = 'gcliArrayMbr';
- this.container.appendChild(element);
-
- // ${field.element}
- var field = getField(this.type.subtype, this.options);
- field.fieldChanged.add(function() {
- var conversion = this.getConversion();
- this.fieldChanged({ conversion: conversion });
- this.setMessage(conversion.message);
- }, this);
-
- if (subConversion) {
- field.setConversion(subConversion);
- }
- element.appendChild(field.element);
-
- // <div class=gcliArrayMbrDel onclick="${_onDel}">
- var delButton = dom.createElement(this.document, 'button');
- delButton.className = 'gcliArrayMbrDel';
- delButton.addEventListener('click', this._onDel, false);
- delButton.innerHTML = l10n.lookup('fieldArrayDel');
- element.appendChild(delButton);
-
- var member = {
- element: element,
- field: field,
- parent: this
- };
- member.onDelete = function() {
- this.parent.container.removeChild(this.element);
- this.parent.members = this.parent.members.filter(function(test) {
- return test !== this;
- });
- this.parent.onInputChange();
- }.bind(member);
- delButton.addEventListener('click', member.onDelete, false);
-
- this.members.push(member);
- };
-
- exports.ArrayField = ArrayField;
- addField(ArrayField);
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/menu', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/argument', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/menu.css', 'text!gcli/ui/menu.html'], function(require, exports, module) {
-
-
- var dom = require('gcli/util').dom;
-
- var Conversion = require('gcli/types').Conversion;
- var Argument = require('gcli/argument').Argument;
- var canon = require('gcli/canon');
-
- var Templater = require('gcli/ui/domtemplate').Templater;
-
- var menuCss = require('text!gcli/ui/menu.css');
- var menuHtml = require('text!gcli/ui/menu.html');
-
-
- /**
- * Menu is a display of the commands that are possible given the state of a
- * requisition.
- * @param document The document from which we create elements.
- * @param options A way to customize the menu display. Valid options are:
- * - field:true Turns the menu display into a drop-down for use inside a
- * JavascriptField.
- */
- function Menu(document, options) {
- this.element = dom.createElement(document, 'div');
- this.element.className = 'gcliMenu';
- if (options && options.field) {
- this.element.className += ' gcliMenuField';
- }
-
- // Pull the HTML into the DOM, but don't add it to the document
- if (menuCss != null) {
- this.style = dom.importCss(menuCss, document);
- }
-
- var templates = dom.createElement(document, 'div');
- dom.setInnerHtml(templates, menuHtml);
- this.optTempl = templates.querySelector('#gcliOptTempl');
-
- // Contains the items that should be displayed
- this.items = null;
- }
-
- /**
- * Avoid memory leaks
- */
- Menu.prototype.destroy = function() {
- if (this.style) {
- this.style.parentNode.removeChild(this.style);
- delete this.style;
- }
-
- delete this.element;
- delete this.items;
- delete this.optTempl;
- };
-
- /**
- * The default is to do nothing when someone clicks on the menu.
- * Plug an implementation in here before calling show() to do something useful.
- * This is called from template.html
- * @param ev The click event from the browser
- */
- Menu.prototype.onItemClick = function(ev) {
- };
-
- /**
- * Display a number of items in the menu (or hide the menu if there is nothing
- * to display)
- * @param items The items to show in the menu
- * @param error An error message to display
- */
- Menu.prototype.show = function(items, error) {
- this.error = error;
- this.items = items;
-
- if (this.error == null && this.items.length === 0) {
- this.element.style.display = 'none';
- return;
- }
-
- var options = this.optTempl.cloneNode(true);
- new Templater().processNode(options, this);
-
- dom.clearElement(this.element);
- this.element.appendChild(options);
-
- this.element.style.display = 'block';
- };
-
- /**
- * Hide the menu
- */
- Menu.prototype.hide = function() {
- this.element.style.display = 'none';
- };
-
- exports.Menu = Menu;
-
-
- /**
- * CommandMenu is a special menu that integrates with a Requisition to display
- * available commands.
- */
- function CommandMenu(document, requisition) {
- Menu.call(this, document);
- this.requisition = requisition;
-
- this.requisition.commandChange.add(this.onCommandChange, this);
- canon.canonChange.add(this.onCommandChange, this);
- }
-
- CommandMenu.prototype = Object.create(Menu.prototype);
-
- /**
- * Avoid memory leaks
- */
- CommandMenu.prototype.destroy = function() {
- this.requisition.commandChange.remove(this.onCommandChange, this);
- canon.canonChange.remove(this.onCommandChange, this);
-
- Menu.prototype.destroy.call(this);
- };
-
- /**
- * We want to fill-in the clicked command in the cli input when the user clicks
- */
- CommandMenu.prototype.onItemClick = function(ev) {
- var type = this.requisition.commandAssignment.param.type;
-
- var text = type.stringify(ev.currentTarget.item);
- var arg = new Argument(text);
- arg.suffix = ' ';
-
- var conversion = type.parse(arg);
- this.requisition.commandAssignment.setConversion(conversion);
- };
-
- /**
- * Update the various hint components to reflect the changed command
- */
- CommandMenu.prototype.onCommandChange = function(ev) {
- var command = this.requisition.commandAssignment.getValue();
- if (!command || !command.exec) {
- var error = this.requisition.commandAssignment.getMessage();
- var predictions = this.requisition.commandAssignment.getPredictions();
-
- if (predictions.length === 0) {
- var commandType = this.requisition.commandAssignment.param.type;
- var conversion = commandType.parse(new Argument());
- predictions = conversion.getPredictions();
- }
-
- predictions.sort(function(command1, command2) {
- return command1.name.localeCompare(command2.name);
- });
- var items = [];
- predictions.forEach(function(item) {
- if (item.description && !item.hidden) {
- items.push(item);
- }
- }, this);
-
- this.show(items, error);
- }
- else {
- if (ev && ev.oldValue === ev.newValue) {
- return; // Just the text has changed
- }
-
- this.hide();
- }
- };
-
- exports.CommandMenu = CommandMenu;
-
-
- });
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) {
-
- Components.utils.import("resource:///modules/devtools/Templater.jsm");
- exports.Templater = Templater;
-
- });
- define("text!gcli/ui/menu.css", [], void 0);
- define("text!gcli/ui/menu.html", [], "" +
- "<!--" +
- "Template for the beginnings of a command menu." +
- "This will work with things other than a command - many things are a set of" +
- "things with a name and description." +
- "In the command context it is evaluated once for every keypress in the cli" +
- "when a command has not been entered." +
- "-->" +
- "<div id=\"gcliOptTempl\" aria-live=\"polite\">" +
- " <div class=\"gcliOption\" foreach=\"item in ${items}\" onclick=\"${onItemClick}\"" +
- " title=\"${item.manual || ''}\">" +
- " ${__element.item = item; ''}" +
- " <span class=\"gcliOptionName\">${item.name}</span>" +
- " <span class=\"gcliOptionDesc\">${item.description}</span>" +
- " </div>" +
- " <div class=\"gcliMenuError\" if=\"${error}\">${error}</div>" +
- "</div>" +
- "");
-
- define("text!gcli/ui/arg_fetch.css", [], void 0);
- define("text!gcli/ui/arg_fetch.html", [], "" +
- "<!--" +
- "Template for an Assignment." +
- "Evaluated each time the commandAssignment changes" +
- "-->" +
- "<div id=\"gcliReqTempl\" aria-live=\"polite\">" +
- " <div>" +
- " <div class=\"gcliCmdDesc\">" +
- " ${requisition.commandAssignment.getValue().description}" +
- " </div>" +
- " <table class=\"gcliParams\">" +
- " <tbody class=\"gcliAssignment\"" +
- " foreach=\"assignment in ${requisition.getAssignments()}\">" +
- " <!-- Parameter -->" +
- " <tr class=\"gcliGroupRow\">" +
- " <td class=\"gcliParamName\">" +
- " <label for=\"gcliForm${assignment.param.name}\">" +
- " ${assignment.param.description ? assignment.param.description + ':' : ''}" +
- " </label>" +
- " </td>" +
- " <td class=\"gcliParamInput\">${getInputFor(assignment)}</td>" +
- " <td>" +
- " <span class=\"gcliRequired\" if=\"${assignment.param.isDataRequired()}\"> *</span>" +
- " </td>" +
- " </tr>" +
- " <tr class=\"gcliGroupRow\">" +
- " <td class=\"gcliParamError\" colspan=\"2\">" +
- " ${linkMessageElement(assignment, __element)}" +
- " </td>" +
- " </tr>" +
- " </tbody>" +
- " <tfoot>" +
- " <tr>" +
- " <td colspan=\"3\" class=\"gcliParamSubmit\">" +
- " <input type=\"submit\" value=\"Cancel\" onclick=\"${onFormCancel}\"/>" +
- " <input type=\"submit\" value=\"OK\" onclick=\"${onFormOk}\" save=\"${okElement}\"/>" +
- " </td>" +
- " </tr>" +
- " </tfoot>" +
- " </table>" +
- " </div>" +
- "</div>" +
- "");
-
- /*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
- define('gcli/ui/focus', ['require', 'exports', 'module' , 'gcli/util'], function(require, exports, module) {
-
-
- var util = require('gcli/util');
-
- /**
- * FocusManager solves the problem of tracking focus among a set of nodes.
- * The specific problem we are solving is when the hint element must be visible
- * if either the command line or any of the inputs in the hint element has the
- * focus, and invisible at other times, without hiding and showing the hint
- * element even briefly as the focus changes between them.
- * It does this simply by postponing the hide events by 250ms to see if
- * something else takes focus.
- * @param options An optional object containing configuration values. Valid
- * properties on the options object are:
- * - document
- * - blurDelay
- * - debug
- * - initialFocus
- */
- function FocusManager(options) {
- options = options || {};
-
- this._debug = options.debug || false;
- this._blurDelayTimeout = null; // Result of setTimeout in delaying a blur
- this._monitoredElements = []; // See addMonitoredElement()
-
- this.hasFocus = false;
- this.blurDelay = options.blurDelay || 250;
- this.document = options.document || document;
-
- this.onFocus = util.createEvent('FocusManager.onFocus');
- this.onBlur = util.createEvent('FocusManager.onBlur');
-
- // We take a focus event anywhere to be an indication that we might be about
- // to lose focus
- this._onDocumentFocus = function() {
- this.reportBlur('document');
- }.bind(this);
- this.document.addEventListener('focus', this._onDocumentFocus, true);
- }
-
- /**
- * Avoid memory leaks
- */
- FocusManager.prototype.destroy = function() {
- this.document.removeEventListener('focus', this._onDocumentFocus, true);
- delete this.document;
-
- for (var i = 0; i < this._monitoredElements.length; i++) {
- var monitor = this._monitoredElements[i];
- console.error('Hanging monitored element: ', monitor.element);
-
- monitor.element.removeEventListener('focus', monitor.onFocus, true);
- monitor.element.removeEventListener('blur', monitor.onBlur, true);
- }
-
- if (this._blurDelayTimeout) {
- clearTimeout(this._blurDelayTimeout);
- this._blurDelayTimeout = null;
- }
- };
-
- /**
- * The easy way to include an element in the set of things that are part of the
- * aggregate focus. Using [add|remove]MonitoredElement() is a simpler way of
- * option than calling report[Focus|Blur]()
- * @param element The element on which to track focus|blur events
- * @param where Optional source string for debugging only
- */
- FocusManager.prototype.addMonitoredElement = function(element, where) {
- if (this._debug) {
- console.log('FocusManager.addMonitoredElement(' + (where || 'unknown') + ')');
- }
-
- var monitor = {
- element: element,
- where: where,
- onFocus: function() { this.reportFocus(where); }.bind(this),
- onBlur: function() { this.reportBlur(where); }.bind(this)
- };
-
- element.addEventListener('focus', monitor.onFocus, true);
- element.addEventListener('blur', monitor.onBlur, true);
- this._monitoredElements.push(monitor);
- };
-
- /**
- * Undo the effects of addMonitoredElement()
- * @param element The element to stop tracking
- */
- FocusManager.prototype.removeMonitoredElement = function(element) {
- var monitor;
- var matchIndex;
-
- for (var i = 0; i < this._monitoredElements.length; i++) {
- if (this._monitoredElements[i].element === element) {
- monitor = this._monitoredElements[i];
- matchIndex = i;
- }
- }
-
- if (!monitor) {
- if (this._debug) {
- console.error('Missing monitor for element. ', element);
- }
- return;
- }
-
- this._monitoredElements.splice(matchIndex, 1);
- element.removeEventListener('focus', monitor.onFocus, true);
- element.removeEventListener('blur', monitor.onBlur, true);
- };
-
- /**
- * Some component has received a 'focus' event. This sets the internal status
- * straight away and informs the listeners
- * @param where Optional source string for debugging only
- */
- FocusManager.prototype.reportFocus = function(where) {
- if (this._debug) {
- console.log('FocusManager.reportFocus(' + (where || 'unknown') + ')');
- }
-
- if (this._blurDelayTimeout) {
- if (this._debug) {
- console.log('FocusManager.cancelBlur');
- }
- clearTimeout(this._blurDelayTimeout);
- this._blurDelayTimeout = null;
- }
-
- if (!this.hasFocus) {
- this.hasFocus = true;
- this.onFocus();
- }
- };
-
- /**
- * Some component has received a 'blur' event. This waits for a while to see if
- * we are going to get any subsequent 'focus' events and then sets the internal
- * status and informs the listeners
- * @param where Optional source string for debugging only
- */
- FocusManager.prototype.reportBlur = function(where) {
- if (this._debug) {
- console.log('FocusManager.reportBlur(' + where + ')');
- }
-
- if (this.hasFocus) {
- if (this._blurDelayTimeout) {
- if (this._debug) {
- console.log('FocusManager.blurPending');
- }
- return;
- }
-
- this._blurDelayTimeout = setTimeout(function() {
- if (this._debug) {
- console.log('FocusManager.blur');
- }
- this.hasFocus = false;
- this.onBlur();
- this._blurDelayTimeout = null;
- }.bind(this), this.blurDelay);
- }
- };
-
- exports.FocusManager = FocusManager;
-
-
- });
-
- /*
- * require GCLI so it can be exported as declared in EXPORTED_SYMBOLS
- * The dependencies specified here should be the same as in Makefile.dryice.js
- */
- var gcli = require("gcli/index");
-